Persisitence and loading for ServiceDiscoveryResult

Stephen Paul Weber created

Change summary

src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java | 68 
src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java     | 41 
2 files changed, 98 insertions(+), 11 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java 🔗

@@ -1,6 +1,7 @@
 package eu.siacs.conversations.entities;
 
 import android.content.ContentValues;
+import android.database.Cursor;
 import android.util.Base64;
 import java.io.UnsupportedEncodingException;
 import java.lang.Comparable;
@@ -17,6 +18,10 @@ import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xmpp.stanzas.IqPacket;
 
 public class ServiceDiscoveryResult {
+	public static final String TABLENAME = "discovery_results";
+	public static final String HASH = "hash";
+	public static final String VER = "ver";
+	public static final String RESULT = "result";
 
 	protected static String blankNull(String s) {
 		return s == null ? "" : s;
@@ -36,10 +41,21 @@ public class ServiceDiscoveryResult {
 		}
 
 		public Identity(final Element el) {
-			this.category = el.getAttribute("category");
-			this.type = el.getAttribute("type");
-			this.lang = el.getAttribute("xml:lang");
-			this.name = el.getAttribute("name");
+			this(
+				el.getAttribute("category"),
+				el.getAttribute("type"),
+				el.getAttribute("xml:lang"),
+				el.getAttribute("name")
+			);
+		}
+
+		public Identity(final JSONObject o) {
+			this(
+				o.optString("category", null),
+				o.optString("type", null),
+				o.optString("lang", null),
+				o.optString("name", null)
+			);
 		}
 
 		public String getCategory() {
@@ -88,17 +104,15 @@ public class ServiceDiscoveryResult {
 		}
 	}
 
+	protected final String hash;
+	protected final byte[] ver;
 	protected final List<Identity> identities;
 	protected final List<String> features;
 
-	public ServiceDiscoveryResult(final List<Identity> identities, final List<String> features) {
-		this.identities = identities;
-		this.features = features;
-	}
-
 	public ServiceDiscoveryResult(final IqPacket packet) {
 		this.identities = new ArrayList<>();
 		this.features = new ArrayList<>();
+		this.hash = "sha-1"; // We only support sha-1 for now
 
 		final List<Element> elements = packet.query().getChildren();
 
@@ -114,6 +128,33 @@ public class ServiceDiscoveryResult {
 				}
 			}
 		}
+
+		this.ver = this.mkCapHash();
+	}
+
+	public ServiceDiscoveryResult(String hash, byte[] ver, JSONObject o) throws JSONException {
+		this.identities = new ArrayList<>();
+		this.features = new ArrayList<>();
+		this.hash = hash;
+		this.ver = ver;
+
+		JSONArray identities = o.optJSONArray("identities");
+		for(int i = 0; i < identities.length(); i++) {
+			this.identities.add(new Identity(identities.getJSONObject(i)));
+		}
+
+		JSONArray features = o.optJSONArray("features");
+		for(int i = 0; i < features.length(); i++) {
+			this.features.add(features.getString(i));
+		}
+	}
+
+	public ServiceDiscoveryResult(Cursor cursor) throws JSONException {
+		this(
+			cursor.getString(cursor.getColumnIndex(HASH)),
+			Base64.decode(cursor.getString(cursor.getColumnIndex(VER)), Base64.DEFAULT),
+			new JSONObject(cursor.getString(cursor.getColumnIndex(RESULT)))
+		);
 	}
 
 	public List<Identity> getIdentities() {
@@ -135,7 +176,7 @@ public class ServiceDiscoveryResult {
 		return false;
 	}
 
-	public byte[] getCapHash() {
+	protected byte[] mkCapHash() {
 		StringBuilder s = new StringBuilder();
 
 		List<Identity> identities = this.getIdentities();
@@ -191,4 +232,11 @@ public class ServiceDiscoveryResult {
 		}
 	}
 
+	public ContentValues getContentValues() {
+		final ContentValues values = new ContentValues();
+		values.put(HASH, this.hash);
+		values.put(VER, new String(Base64.encode(this.ver, Base64.DEFAULT)).trim());
+		values.put(RESULT, this.toJSON().toString());
+		return values;
+	}
 }

src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java 🔗

@@ -31,6 +31,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
+import org.json.JSONException;
 
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.crypto.axolotl.AxolotlService;
@@ -41,6 +42,7 @@ import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.entities.Roster;
+import eu.siacs.conversations.entities.ServiceDiscoveryResult;
 import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 import eu.siacs.conversations.xmpp.jid.Jid;
 
@@ -49,7 +51,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 	private static DatabaseBackend instance = null;
 
 	private static final String DATABASE_NAME = "history";
-	private static final int DATABASE_VERSION = 22;
+	private static final int DATABASE_VERSION = 23;
 
 	private static String CREATE_CONTATCS_STATEMENT = "create table "
 			+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
@@ -63,6 +65,14 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 			+ ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", "
 			+ Contact.JID + ") ON CONFLICT REPLACE);";
 
+	private static String CREATE_DISCOVERY_RESULTS_STATEMENT = "create table "
+			+ ServiceDiscoveryResult.TABLENAME + "("
+			+ ServiceDiscoveryResult.HASH + " TEXT, "
+			+ ServiceDiscoveryResult.VER + " TEXT, "
+			+ ServiceDiscoveryResult.RESULT + " TEXT, "
+			+ "UNIQUE(" + ServiceDiscoveryResult.HASH + ", "
+			+ ServiceDiscoveryResult.VER + ") ON CONFLICT REPLACE);";
+
 	private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE "
 			+ SQLiteAxolotlStore.PREKEY_TABLENAME + "("
 			+ SQLiteAxolotlStore.ACCOUNT + " TEXT,  "
@@ -158,6 +168,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 				+ ") ON DELETE CASCADE);");
 
 		db.execSQL(CREATE_CONTATCS_STATEMENT);
+		db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT);
 		db.execSQL(CREATE_SESSIONS_STATEMENT);
 		db.execSQL(CREATE_PREKEYS_STATEMENT);
 		db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
@@ -355,6 +366,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		if (oldVersion < 22 && newVersion >= 22) {
 			db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.CERTIFICATE);
 		}
+
+		if (oldVersion < 23 && newVersion >= 23) {
+			db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT);
+		}
 	}
 
 	public static synchronized DatabaseBackend getInstance(Context context) {
@@ -379,6 +394,30 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		db.insert(Account.TABLENAME, null, account.getContentValues());
 	}
 
+	public void insertDiscoveryResult(ServiceDiscoveryResult result) {
+		SQLiteDatabase db = this.getWritableDatabase();
+		db.insert(ServiceDiscoveryResult.TABLENAME, null, result.getContentValues());
+	}
+
+	public ServiceDiscoveryResult findDiscoveryResult(final String hash, final String ver) {
+		SQLiteDatabase db = this.getReadableDatabase();
+		String[] selectionArgs = {hash, ver};
+		Cursor cursor = db.query(ServiceDiscoveryResult.TABLENAME, null,
+				ServiceDiscoveryResult.HASH + "=? AND " + ServiceDiscoveryResult.VER + "=?",
+				selectionArgs, null, null, null);
+		if (cursor.getCount() == 0)
+			return null;
+		cursor.moveToFirst();
+
+		ServiceDiscoveryResult result = null;
+		try {
+			result = new ServiceDiscoveryResult(cursor);
+		} catch (JSONException e) { /* result is still null */ }
+
+		cursor.close();
+		return result;
+	}
+
 	public CopyOnWriteArrayList<Conversation> getConversations(int status) {
 		CopyOnWriteArrayList<Conversation> list = new CopyOnWriteArrayList<>();
 		SQLiteDatabase db = this.getReadableDatabase();