Bug 843889 - Factor tabs code that is not Sync-specific out of org.mozilla.gecko.sync. r=rnewman

This commit is contained in:
Nick Alexander 2013-03-06 10:05:39 -08:00
parent 9972ba828c
commit 7f66dd3845
9 changed files with 224 additions and 193 deletions

View File

@ -34,6 +34,8 @@ SYNC_JAVA_FILES := \
background/common/log/writers/StringLogWriter.java \
background/common/log/writers/TagLogWriter.java \
background/common/log/writers/ThreadLocalTagLogWriter.java \
background/db/CursorDumper.java \
background/db/Tab.java \
sync/AlreadySyncingException.java \
sync/CollectionKeys.java \
sync/CommandProcessor.java \
@ -249,7 +251,6 @@ SYNC_JAVA_FILES := \
sync/stage/ServerSyncStage.java \
sync/stage/SyncClientsEngineStage.java \
sync/stage/UploadMetaGlobalStage.java \
sync/StubActivity.java \
sync/syncadapter/SyncAdapter.java \
sync/syncadapter/SyncService.java \
sync/SyncConfiguration.java \

View File

@ -0,0 +1,99 @@
/* 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.background.db;
import android.database.Cursor;
/**
* A utility for dumping a cursor the debug log.
* <p>
* <b>For debugging only!</p>
*/
public class CursorDumper {
protected static String fixedWidth(int width, String s) {
if (s == null) {
return spaces(width);
}
int length = s.length();
if (width == length) {
return s;
}
if (width > length) {
return s + spaces(width - length);
}
return s.substring(0, width);
}
protected static String spaces(int i) {
return " ".substring(0, i);
}
protected static String dashes(int i) {
return "-------------------------------------".substring(0, i);
}
/**
* Dump a cursor to the debug log, ignoring any log level settings.
* <p>
* The position in the cursor is maintained. Caller is responsible for opening
* and closing cursor.
*
* @param cursor
* to dump.
*/
public static void dumpCursor(Cursor cursor) {
dumpCursor(cursor, 18, "records");
}
/**
* Dump a cursor to the debug log, ignoring any log level settings.
* <p>
* The position in the cursor is maintained. Caller is responsible for opening
* and closing cursor.
*
* @param cursor
* to dump.
* @param columnWidth
* how many characters per cursor column.
* @param tags
* a descriptor, printed like "(10 tags)", in the header row.
*/
protected static void dumpCursor(Cursor cursor, int columnWidth, String tags) {
int originalPosition = cursor.getPosition();
try {
String[] columnNames = cursor.getColumnNames();
int columnCount = cursor.getColumnCount();
for (int i = 0; i < columnCount; ++i) {
System.out.print(fixedWidth(columnWidth, columnNames[i]) + " | ");
}
System.out.println("(" + cursor.getCount() + " " + tags + ")");
for (int i = 0; i < columnCount; ++i) {
System.out.print(dashes(columnWidth) + " | ");
}
System.out.println("");
if (!cursor.moveToFirst()) {
System.out.println("EMPTY");
return;
}
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
for (int i = 0; i < columnCount; ++i) {
System.out.print(fixedWidth(columnWidth, cursor.getString(i)) + " | ");
}
System.out.println("");
cursor.moveToNext();
}
for (int i = 0; i < columnCount-1; ++i) {
System.out.print(dashes(columnWidth + 3));
}
System.out.print(dashes(columnWidth + 3 - 1));
System.out.println("");
} finally {
cursor.moveToPosition(originalPosition);
}
}
}

View File

@ -0,0 +1,81 @@
/* 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.background.db;
import org.json.simple.JSONArray;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserContract.Tabs;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.repositories.android.RepoUtils;
import android.content.ContentValues;
import android.database.Cursor;
// Immutable.
public class Tab {
public final String title;
public final String icon;
public final JSONArray history;
public final long lastUsed;
public Tab(String title, String icon, JSONArray history, long lastUsed) {
this.title = title;
this.icon = icon;
this.history = history;
this.lastUsed = lastUsed;
}
public ContentValues toContentValues(String clientGUID, int position) {
ContentValues out = new ContentValues();
out.put(BrowserContract.Tabs.POSITION, position);
out.put(BrowserContract.Tabs.CLIENT_GUID, clientGUID);
out.put(BrowserContract.Tabs.FAVICON, this.icon);
out.put(BrowserContract.Tabs.LAST_USED, this.lastUsed);
out.put(BrowserContract.Tabs.TITLE, this.title);
out.put(BrowserContract.Tabs.URL, (String) this.history.get(0));
out.put(BrowserContract.Tabs.HISTORY, this.history.toJSONString());
return out;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Tab)) {
return false;
}
final Tab other = (Tab) o;
if (!RepoUtils.stringsEqual(this.title, other.title)) {
return false;
}
if (!RepoUtils.stringsEqual(this.icon, other.icon)) {
return false;
}
if (!(this.lastUsed == other.lastUsed)) {
return false;
}
return Utils.sameArrays(this.history, other.history);
}
/**
* Extract a <code>Tab</code> from a cursor row.
* <p>
* Caller is responsible for creating, positioning, and closing the cursor.
*
* @param cursor
* to inspect.
* @return <code>Tab</code> instance.
*/
public static Tab fromCursor(final Cursor cursor) {
final String title = RepoUtils.getStringFromCursor(cursor, Tabs.TITLE);
final String icon = RepoUtils.getStringFromCursor(cursor, Tabs.FAVICON);
final JSONArray history = RepoUtils.getJSONArrayFromCursor(cursor, Tabs.HISTORY);
final long lastUsed = RepoUtils.getLongFromCursor(cursor, Tabs.LAST_USED);
return new Tab(title, icon, history, lastUsed);
}
}

View File

@ -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.sync;
import android.app.Activity;
import android.os.Bundle;
/*
* Activity is just here for testing.
*/
public class StubActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}

View File

@ -7,6 +7,7 @@ package org.mozilla.gecko.sync.repositories.android;
import java.util.List;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.db.CursorDumper;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.sync.repositories.NullCursorException;
import org.mozilla.gecko.sync.repositories.domain.Record;
@ -47,7 +48,7 @@ public abstract class AndroidBrowserRepositoryDataAccessor {
Cursor cur = null;
try {
cur = queryHelper.safeQuery(".dumpDB", null, null, null, null);
RepoUtils.dumpCursor(cur);
CursorDumper.dumpCursor(cur);
} catch (NullCursorException e) {
} finally {
if (cur != null) {

View File

@ -6,10 +6,9 @@ package org.mozilla.gecko.sync.repositories.android;
import java.util.ArrayList;
import org.json.simple.JSONArray;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.db.Tab;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserContract.Tabs;
import org.mozilla.gecko.sync.repositories.InactiveSessionException;
import org.mozilla.gecko.sync.repositories.NoContentProviderException;
import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
@ -22,7 +21,6 @@ import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSince
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
import org.mozilla.gecko.sync.repositories.domain.Record;
import org.mozilla.gecko.sync.repositories.domain.TabsRecord;
import org.mozilla.gecko.sync.repositories.domain.TabsRecord.Tab;
import android.content.ContentProviderClient;
import android.content.ContentValues;
@ -293,24 +291,6 @@ public class FennecTabsRepository extends Repository {
}
}
/**
* Extract a <code>Tab</code> from a cursor row.
* <p>
* Caller is responsible for creating, positioning, and closing the cursor.
*
* @param cursor
* to inspect.
* @return <code>Tab</code> instance.
*/
public static Tab tabFromCursor(final Cursor cursor) {
final String title = RepoUtils.getStringFromCursor(cursor, Tabs.TITLE);
final String icon = RepoUtils.getStringFromCursor(cursor, Tabs.FAVICON);
final JSONArray history = RepoUtils.getJSONArrayFromCursor(cursor, Tabs.HISTORY);
final long lastUsed = RepoUtils.getLongFromCursor(cursor, Tabs.LAST_USED);
return new Tab(title, icon, history, lastUsed);
}
/**
* Extract a <code>TabsRecord</code> from a cursor.
* <p>
@ -330,7 +310,7 @@ public class FennecTabsRepository extends Repository {
public static TabsRecord tabsRecordFromCursor(final Cursor cursor, final String clientGuid, final String clientName) {
final String collection = "tabs";
final TabsRecord record = new TabsRecord(clientGuid, collection, 0, false);
record.tabs = new ArrayList<TabsRecord.Tab>();
record.tabs = new ArrayList<Tab>();
record.clientName = clientName;
record.androidID = -1;
@ -342,7 +322,7 @@ public class FennecTabsRepository extends Repository {
try {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
final Tab tab = FennecTabsRepository.tabFromCursor(cursor);
final Tab tab = Tab.fromCursor(cursor);
record.tabs.add(tab);
if (tab.lastUsed > record.lastModified) {

View File

@ -246,69 +246,6 @@ public class RepoUtils {
return a.equals(b);
}
private static String fixedWidth(int width, String s) {
if (s == null) {
return spaces(width);
}
int length = s.length();
if (width == length) {
return s;
}
if (width > length) {
return s + spaces(width - length);
}
return s.substring(0, width);
}
private static String spaces(int i) {
return " ".substring(0, i);
}
private static String dashes(int i) {
return "-------------------------------------".substring(0, i);
}
public static void dumpCursor(Cursor cur) {
dumpCursor(cur, 18, "records");
}
public static void dumpCursor(Cursor cur, int columnWidth, String tag) {
int originalPosition = cur.getPosition();
try {
String[] columnNames = cur.getColumnNames();
int columnCount = cur.getColumnCount();
for (int i = 0; i < columnCount; ++i) {
System.out.print(fixedWidth(columnWidth, columnNames[i]) + " | ");
}
System.out.println("(" + cur.getCount() + " " + tag + ")");
for (int i = 0; i < columnCount; ++i) {
System.out.print(dashes(columnWidth) + " | ");
}
System.out.println("");
if (!cur.moveToFirst()) {
System.out.println("EMPTY");
return;
}
cur.moveToFirst();
while (!cur.isAfterLast()) {
for (int i = 0; i < columnCount; ++i) {
System.out.print(fixedWidth(columnWidth, cur.getString(i)) + " | ");
}
System.out.println("");
cur.moveToNext();
}
for (int i = 0; i < columnCount-1; ++i) {
System.out.print(dashes(columnWidth + 3));
}
System.out.print(dashes(columnWidth + 3 - 1));
System.out.println("");
} finally {
cur.moveToPosition(originalPosition);
}
}
public static String computeSQLInClause(int items, String field) {
StringBuilder builder = new StringBuilder(field);
builder.append(" IN (");

View File

@ -9,11 +9,11 @@ import java.util.ArrayList;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.db.Tab;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.NonArrayJSONException;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.repositories.android.RepoUtils;
import android.content.ContentValues;
@ -24,88 +24,7 @@ import android.content.ContentValues;
*
*/
public class TabsRecord extends Record {
// Immutable.
public static class Tab {
public final String title;
public final String icon;
public final JSONArray history;
public final long lastUsed;
public Tab(String title, String icon, JSONArray history, long lastUsed) {
this.title = title;
this.icon = icon;
this.history = history;
this.lastUsed = lastUsed;
}
public static Tab fromJSONObject(JSONObject o) throws NonArrayJSONException {
ExtendedJSONObject obj = new ExtendedJSONObject(o);
String title = obj.getString("title");
String icon = obj.getString("icon");
JSONArray history = obj.getArray("urlHistory");
// Last used is inexplicably a string in seconds. Most of the time.
long lastUsed = 0;
Object lU = obj.get("lastUsed");
if (lU instanceof Number) {
lastUsed = ((Long) lU) * 1000L;
} else if (lU instanceof String) {
try {
lastUsed = Long.parseLong((String) lU, 10) * 1000L;
} catch (NumberFormatException e) {
Logger.debug(LOG_TAG, "Invalid number format in lastUsed: " + lU);
}
}
return new Tab(title, icon, history, lastUsed);
}
@SuppressWarnings("unchecked")
public JSONObject toJSONObject() {
JSONObject o = new JSONObject();
o.put("title", title);
o.put("icon", icon);
o.put("urlHistory", history);
o.put("lastUsed", this.lastUsed / 1000);
return o;
}
public ContentValues toContentValues(String clientGUID, int position) {
ContentValues out = new ContentValues();
out.put(BrowserContract.Tabs.POSITION, position);
out.put(BrowserContract.Tabs.CLIENT_GUID, clientGUID);
out.put(BrowserContract.Tabs.FAVICON, this.icon);
out.put(BrowserContract.Tabs.LAST_USED, this.lastUsed);
out.put(BrowserContract.Tabs.TITLE, this.title);
out.put(BrowserContract.Tabs.URL, (String) this.history.get(0));
out.put(BrowserContract.Tabs.HISTORY, this.history.toJSONString());
return out;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Tab)) {
return false;
}
final Tab other = (Tab) o;
if (!RepoUtils.stringsEqual(this.title, other.title)) {
return false;
}
if (!RepoUtils.stringsEqual(this.icon, other.icon)) {
return false;
}
if (!(this.lastUsed == other.lastUsed)) {
return false;
}
return Utils.sameArrays(this.history, other.history);
}
}
private static final String LOG_TAG = "TabsRecord";
public static final String LOG_TAG = "TabsRecord";
public static final String COLLECTION_NAME = "tabs";
public static final long TABS_TTL = 7 * 24 * 60 * 60; // 7 days in seconds.
@ -145,7 +64,7 @@ public class TabsRecord extends Record {
protected static JSONArray tabsToJSON(ArrayList<Tab> tabs) {
JSONArray out = new JSONArray();
for (Tab tab : tabs) {
out.add(tab.toJSONObject());
out.add(tabToJSONObject(tab));
}
return out;
}
@ -155,7 +74,7 @@ public class TabsRecord extends Record {
for (Object o : in) {
if (o instanceof JSONObject) {
try {
tabs.add(Tab.fromJSONObject((JSONObject) o));
tabs.add(TabsRecord.tabFromJSONObject((JSONObject) o));
} catch (NonArrayJSONException e) {
Logger.warn(LOG_TAG, "urlHistory is not an array for this tab.", e);
}
@ -200,4 +119,35 @@ public class TabsRecord extends Record {
}
return out;
}
public static Tab tabFromJSONObject(JSONObject o) throws NonArrayJSONException {
ExtendedJSONObject obj = new ExtendedJSONObject(o);
String title = obj.getString("title");
String icon = obj.getString("icon");
JSONArray history = obj.getArray("urlHistory");
// Last used is inexplicably a string in seconds. Most of the time.
long lastUsed = 0;
Object lU = obj.get("lastUsed");
if (lU instanceof Number) {
lastUsed = ((Long) lU) * 1000L;
} else if (lU instanceof String) {
try {
lastUsed = Long.parseLong((String) lU, 10) * 1000L;
} catch (NumberFormatException e) {
Logger.debug(TabsRecord.LOG_TAG, "Invalid number format in lastUsed: " + lU);
}
}
return new Tab(title, icon, history, lastUsed);
}
@SuppressWarnings("unchecked")
public static JSONObject tabToJSONObject(Tab tab) {
JSONObject o = new JSONObject();
o.put("title", tab.title);
o.put("icon", tab.icon);
o.put("urlHistory", tab.history);
o.put("lastUsed", tab.lastUsed / 1000);
return o;
}
}

View File

@ -22,6 +22,8 @@ background/common/log/writers/SimpleTagLogWriter.java
background/common/log/writers/StringLogWriter.java
background/common/log/writers/TagLogWriter.java
background/common/log/writers/ThreadLocalTagLogWriter.java
background/db/CursorDumper.java
background/db/Tab.java
sync/AlreadySyncingException.java
sync/CollectionKeys.java
sync/CommandProcessor.java
@ -237,7 +239,6 @@ sync/stage/SafeConstrainedServer11Repository.java
sync/stage/ServerSyncStage.java
sync/stage/SyncClientsEngineStage.java
sync/stage/UploadMetaGlobalStage.java
sync/StubActivity.java
sync/syncadapter/SyncAdapter.java
sync/syncadapter/SyncService.java
sync/SyncConfiguration.java