/* * Copyright (C) 2015 Actor LLC. <https://actor.im> */ package im.actor.core.modules.contacts; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import im.actor.core.api.ApiUser; import im.actor.core.api.ApiUserOutPeer; import im.actor.core.api.rpc.RequestGetContacts; import im.actor.core.api.rpc.ResponseGetContacts; import im.actor.core.entity.Contact; import im.actor.core.entity.User; import im.actor.core.modules.api.ApiSupportConfiguration; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.ModuleActor; import im.actor.core.network.RpcCallback; import im.actor.core.network.RpcException; import im.actor.runtime.Crypto; import im.actor.runtime.Log; import im.actor.runtime.actors.messages.Void; import im.actor.runtime.bser.DataInput; import im.actor.runtime.bser.DataOutput; public class ContactsSyncActor extends ModuleActor { private static final String TAG = "ContactsServerSync"; // j2objc workaround private static final Void DUMB = null; private final boolean ENABLE_LOG; private ArrayList<Integer> contacts = new ArrayList<>(); private boolean isInProgress = false; private boolean isInvalidated = false; public ContactsSyncActor(ModuleContext context) { super(context); ENABLE_LOG = context.getConfiguration().isEnableContactsLogging(); } @Override public void preStart() { super.preStart(); if (ENABLE_LOG) { Log.d(TAG, "Loading contacts ids from storage..."); } byte[] data = preferences().getBytes("contact_list"); if (data != null) { try { DataInput dataInput = new DataInput(data, 0, data.length); int count = dataInput.readInt(); for (int i = 0; i < count; i++) { int uid = dataInput.readInt(); if (!contacts.contains(uid)) { contacts.add(uid); } } } catch (IOException e) { e.printStackTrace(); } } notifyState(); self().send(new PerformSync()); } public void performSync() { if (ENABLE_LOG) { Log.d(TAG, "Checking sync"); } if (isInProgress) { if (ENABLE_LOG) { Log.d(TAG, "Sync in progress, invalidating current sync"); } isInvalidated = true; return; } isInProgress = true; isInvalidated = false; if (ENABLE_LOG) { Log.d(TAG, "Starting sync"); } Integer[] uids = contacts.toArray(new Integer[contacts.size()]); Arrays.sort(uids); String hash = ""; for (long u : uids) { if (hash.length() != 0) { hash += ","; } hash += u; } byte[] hashData; try { hashData = hash.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); return; } String hashValue = Crypto.hex(Crypto.SHA256(hashData)); if (ENABLE_LOG) { Log.d(TAG, "Performing sync with hash: " + hashValue); Log.d(TAG, "Performing sync with uids: " + hash); } request(new RequestGetContacts(hashValue, ApiSupportConfiguration.OPTIMIZATIONS), new RpcCallback<ResponseGetContacts>() { @Override public void onResult(ResponseGetContacts response) { if (ENABLE_LOG) { Log.d(TAG, "Sync received " + (response.getUsers().size() + response.getUserPeers().size()) + " contacts"); } if (response.getUserPeers().size() > 0) { updates().loadRequiredPeers(response.getUserPeers(), new ArrayList<>()) .then(v -> onContactsLoaded(response)); } else { updates().applyRelatedData(response.getUsers()) .then(v -> onContactsLoaded(response)); } } @Override public void onError(RpcException e) { isInProgress = false; e.printStackTrace(); } }); } public void onContactsLoaded(ResponseGetContacts result) { if (ENABLE_LOG) { Log.d(TAG, "Sync result received"); } isInProgress = false; context().getConductor().getConductor().onContactsLoaded(); if (result.isNotChanged()) { Log.d(TAG, "Sync: Not changed"); if (isInvalidated) { performSync(); } else { // ProfileSyncState.onContactsLoaded(contactUsers.size() == 0); } return; } // Reading all uids HashSet<Integer> uids = new HashSet<>(); for (ApiUser u : result.getUsers()) { uids.add(u.getId()); } for (ApiUserOutPeer u : result.getUserPeers()) { uids.add(u.getUid()); } if (ENABLE_LOG) { Log.d(TAG, "Sync received " + uids.size() + " contacts"); } outer: for (Integer uid : contacts.toArray(new Integer[contacts.size()])) { for (Integer u : uids) { if (u.equals(uid)) { continue outer; } } if (ENABLE_LOG) { Log.d(TAG, "Removing: #" + uid); } contacts.remove((Integer) uid); if (getUser(uid) != null) { getUserVM(uid).isContact().change(false); } context().getContactsModule().markNonContact(uid); } for (Integer u : uids) { if (contacts.contains(u)) { continue; } if (ENABLE_LOG) { Log.d(TAG, "Adding: #" + u); } contacts.add(u); if (getUser(u) != null) { getUserVM(u).isContact().change(true); } context().getContactsModule().markContact(u); } saveList(); updateEngineList(); if (isInvalidated) { self().send(new PerformSync()); } } public void onContactsAdded(int[] uids) { if (ENABLE_LOG) { Log.d(TAG, "OnContactsAdded received"); } for (int uid : uids) { if (contacts.contains(uid)) { continue; } if (ENABLE_LOG) { Log.d(TAG, "Adding: #" + uid); } contacts.add(uid); context().getContactsModule().markContact(uid); getUserVM(uid).isContact().change(true); } saveList(); updateEngineList(); self().send(new PerformSync()); } public void onContactsRemoved(int[] uids) { if (ENABLE_LOG) { Log.d(TAG, "OnContactsRemoved received"); } for (int uid : uids) { Log.d(TAG, "Removing: #" + uid); contacts.remove((Integer) uid); context().getContactsModule().markNonContact(uid); getUserVM(uid).isContact().change(false); } saveList(); updateEngineList(); self().send(new PerformSync()); } public void onUserChanged(User user) { if (ENABLE_LOG) { Log.d(TAG, "OnUserChanged #" + user.getUid() + " received"); } if (!contacts.contains(user.getUid())) { return; } updateEngineList(); } private void updateEngineList() { if (ENABLE_LOG) { Log.d(TAG, "Saving contact EngineList"); } ArrayList<User> userList = new ArrayList<>(); for (int u : contacts) { userList.add(getUser(u)); } Collections.sort(userList, (lhs, rhs) -> lhs.getName().compareTo(rhs.getName())); List<Contact> registeredContacts = new ArrayList<>(); int index = -1; for (User userModel : userList) { Contact contact = new Contact(userModel.getUid(), (long) index--, userModel.getAvatar(), userModel.getName()); registeredContacts.add(contact); } context().getContactsModule().getContacts().replaceItems(registeredContacts); Integer[] sorted = new Integer[contacts.size()]; int sindex = 0; for (User userModel : userList) { sorted[sindex++] = userModel.getUid(); } context().getSearchModule().onContactsChanged(sorted); notifyState(); } private void saveList() { if (ENABLE_LOG) { Log.d(TAG, "Saving contacts ids to storage"); } DataOutput dataOutput = new DataOutput(); dataOutput.writeInt(contacts.size()); for (int l : contacts) { dataOutput.writeInt(l); } preferences().putBytes("contact_list", dataOutput.toByteArray()); } private void notifyState() { context().getConductor().getConductor().onContactsChanged(context().getContactsModule().getContacts().isEmpty()); } @Override public void onReceive(Object message) { if (message instanceof ContactsAdded) { onContactsAdded(((ContactsAdded) message).getUids()); } else if (message instanceof ContactsRemoved) { onContactsRemoved(((ContactsRemoved) message).getUids()); } else if (message instanceof UserChanged) { onUserChanged(((UserChanged) message).getUser()); } else if (message instanceof PerformSync) { performSync(); } else { super.onReceive(message); } } private static class PerformSync { } public static class ContactsAdded { private int[] uids; public ContactsAdded(int[] uids) { this.uids = uids; } public int[] getUids() { return uids; } } public static class ContactsRemoved { private int[] uids; public ContactsRemoved(int[] uids) { this.uids = uids; } public int[] getUids() { return uids; } } public static class UserChanged { private User user; public UserChanged(User user) { this.user = user; } public User getUser() { return user; } } }