/* 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.repositories.domain;
import java.util.ArrayList;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.Logger;
import org.mozilla.gecko.sync.NonArrayJSONException;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.repositories.android.RepoUtils;
import android.content.ContentValues;
/**
* Represents a client's collection of tabs.
*
* @author rnewman
*
*/
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 COLLECTION_NAME = "tabs";
public static final long TABS_TTL = 7 * 24 * 60 * 60; // 7 days in seconds.
public TabsRecord(String guid, String collection, long lastModified, boolean deleted) {
super(guid, collection, lastModified, deleted);
this.ttl = TABS_TTL;
}
public TabsRecord(String guid, String collection, long lastModified) {
this(guid, collection, lastModified, false);
}
public TabsRecord(String guid, String collection) {
this(guid, collection, 0, false);
}
public TabsRecord(String guid) {
this(guid, COLLECTION_NAME, 0, false);
}
public TabsRecord() {
this(Utils.generateGuid(), COLLECTION_NAME, 0, false);
}
public String clientName;
public ArrayList<Tab> tabs;
@Override
public void initFromPayload(ExtendedJSONObject payload) {
clientName = (String) payload.get("clientName");
try {
tabs = tabsFrom(payload.getArray("tabs"));
} catch (NonArrayJSONException e) {
// Oh well.
tabs = new ArrayList<Tab>();
}
}
@SuppressWarnings("unchecked")
protected static JSONArray tabsToJSON(ArrayList<Tab> tabs) {
JSONArray out = new JSONArray();
for (Tab tab : tabs) {
out.add(tab.toJSONObject());
}
return out;
}
protected static ArrayList<Tab> tabsFrom(JSONArray in) {
ArrayList<Tab> tabs = new ArrayList<Tab>(in.size());
for (Object o : in) {
if (o instanceof JSONObject) {
try {
tabs.add(Tab.fromJSONObject((JSONObject) o));
} catch (NonArrayJSONException e) {
Logger.warn(LOG_TAG, "urlHistory is not an array for this tab.", e);
}
}
}
return tabs;
}
@Override
public void populatePayload(ExtendedJSONObject payload) {
putPayload(payload, "id", this.guid);
putPayload(payload, "clientName", this.clientName);
payload.put("tabs", tabsToJSON(this.tabs));
}
@Override
public Record copyWithIDs(String guid, long androidID) {
TabsRecord out = new TabsRecord(guid, this.collection, this.lastModified, this.deleted);
out.androidID = androidID;
out.sortIndex = this.sortIndex;
out.ttl = this.ttl;
out.clientName = this.clientName;
out.tabs = new ArrayList<Tab>(this.tabs);
return out;
}
public ContentValues getClientsContentValues() {
ContentValues cv = new ContentValues();
cv.put(BrowserContract.Clients.GUID, this.guid);
cv.put(BrowserContract.Clients.NAME, this.clientName);
cv.put(BrowserContract.Clients.LAST_MODIFIED, this.lastModified);
return cv;
}
public ContentValues[] getTabsContentValues() {
int c = tabs.size();
ContentValues[] out = new ContentValues[c];
for (int i = 0; i < c; i++) {
out[i] = tabs.get(i).toContentValues(this.guid, i);
}
return out;
}
}