added hash for status quo to make sync reply more performant

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/Config.java                                |   2 
src/quicksy/java/eu/siacs/conversations/android/PhoneNumberContact.java         |  11 
src/quicksy/java/eu/siacs/conversations/entities/Entry.java                     | 114 
src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java |  71 
4 files changed, 141 insertions(+), 57 deletions(-)

Detailed changes

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

@@ -48,7 +48,7 @@ public final class Config {
 
 	public static final boolean ALLOW_NON_TLS_CONNECTIONS = false; //very dangerous. you should have a good reason to set this to true
 
-	public static final long CONTACT_SYNC_RETRY_INTERVAL = 1000L * 60 * 5;
+	public static final long CONTACT_SYNC_RETRY_INTERVAL = 1000L * 60 * 1;
 
 
 	//Notification settings

src/quicksy/java/eu/siacs/conversations/android/PhoneNumberContact.java 🔗

@@ -4,10 +4,12 @@ import android.Manifest;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
+import android.net.Uri;
 import android.os.Build;
 import android.provider.ContactsContract;
 import android.util.Log;
 
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -65,4 +67,13 @@ public class PhoneNumberContact extends AbstractPhoneContact {
         }
         return contacts;
     }
+
+    public static PhoneNumberContact findByUri(Collection<PhoneNumberContact> haystack, Uri needle) {
+        for(PhoneNumberContact contact : haystack) {
+            if (needle.equals(contact.getLookupUri())) {
+                return contact;
+            }
+        }
+        return null;
+    }
 }

src/quicksy/java/eu/siacs/conversations/entities/Entry.java 🔗

@@ -0,0 +1,114 @@
+package eu.siacs.conversations.entities;
+
+import android.util.Base64;
+import android.util.Log;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.android.PhoneNumberContact;
+import eu.siacs.conversations.xml.Element;
+import rocks.xmpp.addr.Jid;
+
+public class Entry implements Comparable<Entry> {
+    private final List<Jid> jids;
+    private final String number;
+
+    private Entry(String number, List<Jid> jids) {
+        this.number = number;
+        this.jids = jids;
+    }
+
+    public static Entry of(Element element) {
+        final String number = element.getAttribute("number");
+        final List<Jid> jids = new ArrayList<>();
+        for (Element jidElement : element.getChildren()) {
+            String content = jidElement.getContent();
+            if (content != null) {
+                jids.add(Jid.of(content));
+            }
+        }
+        return new Entry(number, jids);
+    }
+
+    public static List<Entry> ofPhoneBook(Element phoneBook) {
+        List<Entry> entries = new ArrayList<>();
+        for (Element entry : phoneBook.getChildren()) {
+            if ("entry".equals(entry.getName())) {
+                entries.add(of(entry));
+            }
+        }
+        return entries;
+    }
+
+    public static String statusQuo(final Collection<PhoneNumberContact> phoneNumberContacts, Collection<Contact> systemContacts) {
+        return statusQuo(ofPhoneNumberContactsAndContacts(phoneNumberContacts, systemContacts));
+    }
+
+    private static String statusQuo(final List<Entry> entries) {
+        Collections.sort(entries);
+        StringBuilder builder = new StringBuilder();
+        for(Entry entry : entries) {
+            if (builder.length() != 0) {
+                builder.append('\u001d');
+            }
+            builder.append(entry.getNumber());
+            List<Jid> jids = entry.getJids();
+            Collections.sort(jids);
+            for(Jid jid : jids) {
+                builder.append('\u001e');
+                builder.append(jid.asBareJid().toEscapedString());
+            }
+        }
+        MessageDigest md;
+        try {
+            md = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException e) {
+            return "";
+        }
+        Log.d(Config.LOGTAG,"status quo string: "+builder.toString());
+        byte[] sha1 = md.digest(builder.toString().getBytes());
+        return new String(Base64.encode(sha1, Base64.DEFAULT)).trim();
+    }
+
+    private static List<Entry> ofPhoneNumberContactsAndContacts(final Collection<PhoneNumberContact> phoneNumberContacts, Collection<Contact> systemContacts) {
+        ArrayList<Entry> entries = new ArrayList<>();
+        for(Contact contact : systemContacts) {
+            PhoneNumberContact phoneNumberContact = PhoneNumberContact.findByUri(phoneNumberContacts, contact.getSystemAccount());
+            if (phoneNumberContact != null && phoneNumberContact.getPhoneNumber() != null) {
+                Entry entry = findOrCreateByPhoneNumber(entries, phoneNumberContact.getPhoneNumber());
+                entry.jids.add(contact.getJid().asBareJid());
+            }
+        }
+        return entries;
+    }
+
+    private static Entry findOrCreateByPhoneNumber(final List<Entry> entries, String number) {
+        for(Entry entry : entries) {
+            if (entry.number.equals(number)) {
+                return entry;
+            }
+        }
+        Entry entry = new Entry(number, new ArrayList<>());
+        entries.add(entry);
+        return entry;
+    }
+
+    public List<Jid> getJids() {
+        return jids;
+    }
+
+    public String getNumber() {
+        return number;
+    }
+
+    @Override
+    public int compareTo(Entry o) {
+        return number.compareTo(o.number);
+    }
+}

src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java 🔗

@@ -34,6 +34,7 @@ import eu.siacs.conversations.android.PhoneNumberContact;
 import eu.siacs.conversations.crypto.sasl.Plain;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Contact;
+import eu.siacs.conversations.entities.Entry;
 import eu.siacs.conversations.utils.AccountUtils;
 import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
@@ -286,7 +287,7 @@ public class QuickConversationsService extends AbstractQuickConversationsService
             if (uri == null) {
                 continue;
             }
-            PhoneNumberContact phoneNumberContact = findByUri(contacts, uri);
+            PhoneNumberContact phoneNumberContact = PhoneNumberContact.findByUri(contacts, uri);
             final boolean needsCacheClean;
             if (phoneNumberContact != null) {
                 needsCacheClean = contact.setPhoneContact(phoneNumberContact);
@@ -320,13 +321,17 @@ public class QuickConversationsService extends AbstractQuickConversationsService
         }
         IqPacket query = new IqPacket(IqPacket.TYPE.GET);
         query.setTo(syncServer);
-        query.addChild(new Element("phone-book", Namespace.SYNCHRONIZATION).setChildren(entries));
+        Element book = new Element("phone-book", Namespace.SYNCHRONIZATION).setChildren(entries);
+        String statusQuo = Entry.statusQuo(contacts.values(), account.getRoster().getWithSystemAccounts(PhoneNumberContact.class));
+        Log.d(Config.LOGTAG,"status quo="+statusQuo);
+        book.setAttribute("ver",statusQuo);
+        query.addChild(book);
         mLastSyncAttempt = Attempt.create(hash);
         service.sendIqPacket(account, query, (a, response) -> {
             if (response.getType() == IqPacket.TYPE.RESULT) {
-                List<Contact> withSystemAccounts = account.getRoster().getWithSystemAccounts(PhoneNumberContact.class);
                 final Element phoneBook = response.findChild("phone-book", Namespace.SYNCHRONIZATION);
                 if (phoneBook != null) {
+                    List<Contact> withSystemAccounts = account.getRoster().getWithSystemAccounts(PhoneNumberContact.class);
                     for(Entry entry : Entry.ofPhoneBook(phoneBook)) {
                         PhoneNumberContact phoneContact = contacts.get(entry.getNumber());
                         if (phoneContact == null) {
@@ -341,12 +346,14 @@ public class QuickConversationsService extends AbstractQuickConversationsService
                             withSystemAccounts.remove(contact);
                         }
                     }
-                }
-                for (Contact contact : withSystemAccounts) {
-                    final boolean needsCacheClean = contact.unsetPhoneContact(PhoneNumberContact.class);
-                    if (needsCacheClean) {
-                        service.getAvatarService().clear(contact);
+                    for (Contact contact : withSystemAccounts) {
+                        final boolean needsCacheClean = contact.unsetPhoneContact(PhoneNumberContact.class);
+                        if (needsCacheClean) {
+                            service.getAvatarService().clear(contact);
+                        }
                     }
+                } else {
+                    Log.d(Config.LOGTAG,account.getJid().asBareJid()+": phone number contact list remains unchanged");
                 }
             }
             service.syncRoster(account);
@@ -355,14 +362,6 @@ public class QuickConversationsService extends AbstractQuickConversationsService
         return true;
     }
 
-    private static PhoneNumberContact findByUri(Collection<PhoneNumberContact> haystack, Uri needle) {
-        for(PhoneNumberContact contact : haystack) {
-            if (needle.equals(contact.getLookupUri())) {
-                return contact;
-            }
-        }
-        return null;
-    }
 
     private static class Attempt {
         private final long timestamp;
@@ -382,46 +381,6 @@ public class QuickConversationsService extends AbstractQuickConversationsService
         }
     }
 
-    public static class Entry {
-        private final List<Jid> jids;
-        private final String number;
-
-        private Entry(String number, List<Jid> jids) {
-            this.number = number;
-            this.jids = jids;
-        }
-
-        public static Entry of(Element element) {
-            final String number = element.getAttribute("number");
-            final List<Jid> jids = new ArrayList<>();
-            for (Element jidElement : element.getChildren()) {
-                String content = jidElement.getContent();
-                if (content != null) {
-                    jids.add(Jid.of(content));
-                }
-            }
-            return new Entry(number, jids);
-        }
-
-        public static List<Entry> ofPhoneBook(Element phoneBook) {
-            List<Entry> entries = new ArrayList<>();
-            for (Element entry : phoneBook.getChildren()) {
-                if ("entry".equals(entry.getName())) {
-                    entries.add(of(entry));
-                }
-            }
-            return entries;
-        }
-
-        public List<Jid> getJids() {
-            return jids;
-        }
-
-        public String getNumber() {
-            return number;
-        }
-    }
-
     public interface OnVerificationRequested {
         void onVerificationRequestFailed(int code);