properly restore LMC edits. switch to LMC v1.1

Daniel Gultsch created

fixes #3566

closes #3592

Change summary

src/main/java/eu/siacs/conversations/Config.java                      |  2 
src/main/java/eu/siacs/conversations/entities/Edit.java               | 97 
src/main/java/eu/siacs/conversations/entities/Edited.java             | 80 
src/main/java/eu/siacs/conversations/entities/Message.java            | 21 
src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java |  6 
src/main/java/eu/siacs/conversations/utils/CursorUtils.java           |  2 
6 files changed, 116 insertions(+), 92 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/Config.java 🔗

@@ -117,7 +117,7 @@ public final class Config {
     public static final boolean IGNORE_ID_REWRITE_IN_MUC = true;
     public static final boolean MUC_LEAVE_BEFORE_JOIN = true;
 
-    public static final boolean USE_LMC_VERSION_1_1 = false;
+    public static final boolean USE_LMC_VERSION_1_1 = true;
 
     public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY * 5;
     public static final int MAM_MAX_MESSAGES = 750;

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

@@ -0,0 +1,97 @@
+package eu.siacs.conversations.entities;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Edit {
+
+    private final String editedId;
+    private final String serverMsgId;
+
+    Edit(String editedId, String serverMsgId) {
+        this.editedId = editedId;
+        this.serverMsgId = serverMsgId;
+    }
+
+    static String toJson(List<Edit> edits) throws JSONException {
+        JSONArray jsonArray = new JSONArray();
+        for (Edit edit : edits) {
+            jsonArray.put(edit.toJson());
+        }
+        return jsonArray.toString();
+    }
+
+    static boolean wasPreviouslyEditedRemoteMsgId(List<Edit> edits, String remoteMsgId) {
+        for (Edit edit : edits) {
+            if (edit.editedId != null && edit.editedId.equals(remoteMsgId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    static boolean wasPreviouslyEditedServerMsgId(List<Edit> edits, String serverMsgId) {
+        for (Edit edit : edits) {
+            if (edit.serverMsgId != null && edit.serverMsgId.equals(serverMsgId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static Edit fromJson(JSONObject jsonObject) throws JSONException {
+        String edited = jsonObject.has("edited_id") ? jsonObject.getString("edited_id") : null;
+        String serverMsgId = jsonObject.has("server_msg_id") ? jsonObject.getString("server_msg_id") : null;
+        return new Edit(edited, serverMsgId);
+    }
+
+    static List<Edit> fromJson(String input) {
+        final ArrayList<Edit> list = new ArrayList<>();
+        if (input == null) {
+            return list;
+        }
+        try {
+            final JSONArray jsonArray = new JSONArray(input);
+            for (int i = 0; i < jsonArray.length(); ++i) {
+                list.add(fromJson(jsonArray.getJSONObject(i)));
+            }
+            return list;
+        } catch (JSONException e) {
+            return list;
+        }
+    }
+
+    private JSONObject toJson() throws JSONException {
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("edited_id", editedId);
+        jsonObject.put("server_msg_id", serverMsgId);
+        return jsonObject;
+    }
+
+    String getEditedId() {
+        return editedId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Edit edit = (Edit) o;
+
+        if (editedId != null ? !editedId.equals(edit.editedId) : edit.editedId != null)
+            return false;
+        return serverMsgId != null ? serverMsgId.equals(edit.serverMsgId) : edit.serverMsgId == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = editedId != null ? editedId.hashCode() : 0;
+        result = 31 * result + (serverMsgId != null ? serverMsgId.hashCode() : 0);
+        return result;
+    }
+}

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

@@ -1,80 +0,0 @@
-package eu.siacs.conversations.entities;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class Edited {
-
-    private final String editedId;
-    private final String serverMsgId;
-
-    public Edited(String editedId, String serverMsgId) {
-        this.editedId = editedId;
-        this.serverMsgId = serverMsgId;
-    }
-
-    public static String toJson(List<Edited> edits) throws JSONException {
-        JSONArray jsonArray = new JSONArray();
-        for (Edited edited : edits) {
-            jsonArray.put(edited.toJson());
-        }
-        return jsonArray.toString();
-    }
-
-    public static boolean wasPreviouslyEditedRemoteMsgId(List<Edited> editeds, String remoteMsgId) {
-        for (Edited edited : editeds) {
-            if (edited.editedId != null && edited.editedId.equals(remoteMsgId)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public static boolean wasPreviouslyEditedServerMsgId(List<Edited> editeds, String serverMsgId) {
-        for (Edited edited : editeds) {
-            if (edited.serverMsgId != null && edited.serverMsgId.equals(serverMsgId)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public static Edited fromJson(JSONObject jsonObject) throws JSONException {
-        String edited = jsonObject.getString("edited_id");
-        String serverMsgId = jsonObject.getString("server_msg_id");
-        return new Edited(edited, serverMsgId);
-    }
-
-    public static List<Edited> fromJson(String input) {
-        ArrayList<Edited> list = new ArrayList<>();
-        if (input == null) {
-            return list;
-        }
-        try {
-            JSONArray jsonArray = new JSONArray(input);
-            for (int i = 0; i < jsonArray.length(); ++i) {
-                list.add(fromJson(jsonArray.getJSONObject(i)));
-            }
-
-        } catch (JSONException e) {
-            list = new ArrayList<>();
-            list.add(new Edited(input, null));
-        }
-        return list;
-    }
-
-    public JSONObject toJson() throws JSONException {
-        JSONObject jsonObject = new JSONObject();
-        jsonObject.put("edited_id", editedId);
-        jsonObject.put("server_msg_id", serverMsgId);
-        return jsonObject;
-    }
-
-    public String getEditedId() {
-        return editedId;
-    }
-}

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

@@ -97,7 +97,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
 	protected boolean deleted = false;
 	protected boolean carbon = false;
 	protected boolean oob = false;
-	protected List<Edited> edits = new ArrayList<>();
+	protected List<Edit> edits = new ArrayList<>();
 	protected String relativeFilePath;
 	protected boolean read = true;
 	protected String remoteMsgId = null;
@@ -174,7 +174,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
 		this.serverMsgId = serverMsgId;
 		this.axolotlFingerprint = fingerprint;
 		this.read = read;
-		this.edits = Edited.fromJson(edited);
+		this.edits = Edit.fromJson(edited);
 		this.oob = oob;
 		this.errorMessage = errorMessage;
 		this.readByMarkers = readByMarkers == null ? new HashSet<>() : readByMarkers;
@@ -263,7 +263,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
 		values.put(FINGERPRINT, axolotlFingerprint);
 		values.put(READ, read ? 1 : 0);
 		try {
-			values.put(EDITED, Edited.toJson(edits));
+			values.put(EDITED, Edit.toJson(edits));
 		} catch (JSONException e) {
 			Log.e(Config.LOGTAG,"error persisting json for edits",e);
 		}
@@ -434,11 +434,14 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
 	}
 
 	public void putEdited(String edited, String serverMsgId) {
-		this.edits.add(new Edited(edited, serverMsgId));
+		final Edit edit = new Edit(edited, serverMsgId);
+		if (this.edits.size() < 128 && !this.edits.contains(edit)) {
+			this.edits.add(edit);
+		}
 	}
 
-	public boolean remoteMsgIdMatchInEdit(String id) {
-		for(Edited edit : this.edits) {
+	boolean remoteMsgIdMatchInEdit(String id) {
+		for(Edit edit : this.edits) {
 			if (id.equals(edit.getEditedId())) {
 				return true;
 			}
@@ -507,8 +510,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
 
 	boolean similar(Message message) {
 		if (!isPrivateMessage() && this.serverMsgId != null && message.getServerMsgId() != null) {
-			return this.serverMsgId.equals(message.getServerMsgId()) || Edited.wasPreviouslyEditedServerMsgId(edits, message.getServerMsgId());
-		} else if (Edited.wasPreviouslyEditedServerMsgId(edits, message.getServerMsgId())) {
+			return this.serverMsgId.equals(message.getServerMsgId()) || Edit.wasPreviouslyEditedServerMsgId(edits, message.getServerMsgId());
+		} else if (Edit.wasPreviouslyEditedServerMsgId(edits, message.getServerMsgId())) {
 			return true;
 		} else if (this.body == null || this.counterpart == null) {
 			return false;
@@ -524,7 +527,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
 			final boolean matchingCounterpart = this.counterpart.equals(message.getCounterpart());
 			if (message.getRemoteMsgId() != null) {
 				final boolean hasUuid = CryptoHelper.UUID_PATTERN.matcher(message.getRemoteMsgId()).matches();
-				if (hasUuid && matchingCounterpart && Edited.wasPreviouslyEditedRemoteMsgId(edits, message.getRemoteMsgId())) {
+				if (hasUuid && matchingCounterpart && Edit.wasPreviouslyEditedRemoteMsgId(edits, message.getRemoteMsgId())) {
 					return true;
 				}
 				return (message.getRemoteMsgId().equals(this.remoteMsgId) || message.getRemoteMsgId().equals(this.uuid))

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

@@ -63,7 +63,7 @@ import rocks.xmpp.addr.Jid;
 public class DatabaseBackend extends SQLiteOpenHelper {
 
     private static final String DATABASE_NAME = "history";
-    private static final int DATABASE_VERSION = 45;
+    private static final int DATABASE_VERSION = 46;
     private static DatabaseBackend instance = null;
     private static String CREATE_CONTATCS_STATEMENT = "create table "
             + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
@@ -546,6 +546,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
         if (oldVersion < 45 && newVersion >= 45) {
             db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.BODY_LANGUAGE);
         }
+
+        if (oldVersion < 46 && newVersion >= 46) {
+            db.execSQL("update "+Message.TABLENAME+" set "+Message.EDITED+"=NULL");
+        }
     }
 
     private void canonicalizeJids(SQLiteDatabase db) {

src/main/java/eu/siacs/conversations/utils/CursorUtils.java 🔗

@@ -11,7 +11,7 @@ public class CursorUtils {
         if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
             if (cursor instanceof AbstractWindowedCursor) {
                 final AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor) cursor;
-                windowedCursor.setWindow(new CursorWindow("8M", 8 * 1024 * 1024));
+                windowedCursor.setWindow(new CursorWindow("4M", 8 * 1024 * 1024));
             }
             if (cursor instanceof SQLiteCursor) {
                 ((SQLiteCursor) cursor).setFillWindowForwardOnly(true);