From a1a625bb2d4e83b9d9ca34e071d2314656e11d42 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 29 Oct 2018 14:42:43 +0100 Subject: [PATCH] added hash for status quo to make sync reply more performant --- .../java/eu/siacs/conversations/Config.java | 2 +- .../android/PhoneNumberContact.java | 11 ++ .../siacs/conversations/entities/Entry.java | 114 ++++++++++++++++++ .../services/QuickConversationsService.java | 71 +++-------- 4 files changed, 141 insertions(+), 57 deletions(-) create mode 100644 src/quicksy/java/eu/siacs/conversations/entities/Entry.java diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 0bb43be38..d43622f0e 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/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 diff --git a/src/quicksy/java/eu/siacs/conversations/android/PhoneNumberContact.java b/src/quicksy/java/eu/siacs/conversations/android/PhoneNumberContact.java index e044746a7..4515ec299 100644 --- a/src/quicksy/java/eu/siacs/conversations/android/PhoneNumberContact.java +++ b/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 haystack, Uri needle) { + for(PhoneNumberContact contact : haystack) { + if (needle.equals(contact.getLookupUri())) { + return contact; + } + } + return null; + } } diff --git a/src/quicksy/java/eu/siacs/conversations/entities/Entry.java b/src/quicksy/java/eu/siacs/conversations/entities/Entry.java new file mode 100644 index 000000000..83bbe1985 --- /dev/null +++ b/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 { + private final List jids; + private final String number; + + private Entry(String number, List jids) { + this.number = number; + this.jids = jids; + } + + public static Entry of(Element element) { + final String number = element.getAttribute("number"); + final List 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 ofPhoneBook(Element phoneBook) { + List 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 phoneNumberContacts, Collection systemContacts) { + return statusQuo(ofPhoneNumberContactsAndContacts(phoneNumberContacts, systemContacts)); + } + + private static String statusQuo(final List entries) { + Collections.sort(entries); + StringBuilder builder = new StringBuilder(); + for(Entry entry : entries) { + if (builder.length() != 0) { + builder.append('\u001d'); + } + builder.append(entry.getNumber()); + List 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 ofPhoneNumberContactsAndContacts(final Collection phoneNumberContacts, Collection systemContacts) { + ArrayList 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 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 getJids() { + return jids; + } + + public String getNumber() { + return number; + } + + @Override + public int compareTo(Entry o) { + return number.compareTo(o.number); + } +} diff --git a/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java b/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java index b391361cb..e34099f92 100644 --- a/src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java +++ b/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 withSystemAccounts = account.getRoster().getWithSystemAccounts(PhoneNumberContact.class); final Element phoneBook = response.findChild("phone-book", Namespace.SYNCHRONIZATION); if (phoneBook != null) { + List 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 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 jids; - private final String number; - - private Entry(String number, List jids) { - this.number = number; - this.jids = jids; - } - - public static Entry of(Element element) { - final String number = element.getAttribute("number"); - final List 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 ofPhoneBook(Element phoneBook) { - List entries = new ArrayList<>(); - for (Element entry : phoneBook.getChildren()) { - if ("entry".equals(entry.getName())) { - entries.add(of(entry)); - } - } - return entries; - } - - public List getJids() { - return jids; - } - - public String getNumber() { - return number; - } - } - public interface OnVerificationRequested { void onVerificationRequestFailed(int code);