package org.mercury_im.messenger.core.stores; import org.jivesoftware.smack.roster.packet.RosterPacket; import org.jxmpp.jid.Jid; import org.mercury_im.messenger.xmpp.model.AccountModel; import org.mercury_im.messenger.xmpp.model.ContactModel; import org.mercury_im.messenger.xmpp.model.EntityModel; import org.mercury_im.messenger.xmpp.repository.RequeryAccountRepository; import org.mercury_im.messenger.xmpp.repository.RosterRepository; import org.mercury_im.messenger.xmpp.enums.SubscriptionDirection; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.inject.Inject; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.schedulers.Schedulers; public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.RosterStore { private static final Logger LOGGER = Logger.getLogger(RosterStore.class.getName()); private final RosterRepository rosterRepository; private final RequeryAccountRepository accountRepository; private AccountModel account; private CompositeDisposable disposable = null; private final Map itemMap = new HashMap<>(); private String rosterVersion; @Inject public RosterStore(RosterRepository rosterRepository, RequeryAccountRepository accountRepository) { this.rosterRepository = rosterRepository; this.accountRepository = accountRepository; } public void subscribe() { LOGGER.log(Level.INFO, "Subscribing..."); if (disposable != null) { return; } disposable = new CompositeDisposable(); disposable.add(rosterRepository.getAllContactsOfAccount(account) .observeOn(Schedulers.computation()) .subscribe(contactsList -> { itemMap.clear(); for (ContactModel contactModel : contactsList) { itemMap.put(contactModel.getEntity().getJid(), fromModel(contactModel)); LOGGER.log(Level.INFO, "Populate itemMap with " + contactsList.toList().size() + " items"); } }, error -> LOGGER.log(Level.WARNING, "An error occurred while updating roster cache", error))); disposable.add(rosterRepository.getRosterVersion(account) .observeOn(Schedulers.computation()) .subscribe( result -> setRosterVersion(result), error -> LOGGER.log(Level.WARNING, "An error occurred updating cached roster version", error))); } public void unsubscribe() { if (disposable == null) { return; } disposable.dispose(); disposable = null; } public void setAccountId(long accountId) { this.account = accountRepository.getAccount(accountId) .doOnSubscribe(subscribe -> LOGGER.log(Level.FINE, "Fetching account " + accountId)) .blockingFirst().first(); } private void setRosterVersion(String rosterVersion) { this.rosterVersion = rosterVersion; } @Override public List getEntries() { return new ArrayList<>(itemMap.values()); } @Override public RosterPacket.Item getEntry(Jid bareJid) { return itemMap.get(bareJid); } @Override public String getRosterVersion() { return rosterVersion != null ? rosterVersion : ""; } @Override public boolean addEntry(RosterPacket.Item item, String version) { LOGGER.log(Level.INFO, "Add entry " + item.toXML().toString()); // Update database ContactModel contact = toModel(item); disposable.add(rosterRepository.upsertContact(contact) .subscribe( success -> LOGGER.log(Level.FINE, "Upserted contact model " + success + " successfully"), error -> LOGGER.log(Level.WARNING, "An error occurred upserting contact " + contact, error) )); disposable.add(rosterRepository.updateRosterVersion(account, version) .subscribe( success -> LOGGER.log(Level.FINE, "Upserted roster version to " + rosterVersion + " successfully"), error -> LOGGER.log(Level.WARNING, "An error occurred upserting roster version", error) )); return true; } @Override public boolean resetEntries(Collection items, String version) { LOGGER.log(Level.INFO, "Reset Entries: " + Arrays.toString(items.toArray())); // Update database // TODO: Delete other contacts for (RosterPacket.Item item : items) { ContactModel model = toModel(item); disposable.add(rosterRepository.upsertContact(model) .subscribe( success -> LOGGER.log(Level.FINE, "Upserted contact model " + success + " successfully"), error -> LOGGER.log(Level.WARNING, "An error occurred upserting contact " + model, error) )); } disposable.add(rosterRepository.updateRosterVersion(account, version) .subscribe( success -> LOGGER.log(Level.FINE, "Upserted roster version to " + rosterVersion + " successfully"), error -> LOGGER.log(Level.WARNING, "An error occurred upserting roster version", error) )); return true; } @Override public boolean removeEntry(Jid bareJid, String version) { LOGGER.log(Level.INFO, "Remove entry " + bareJid.toString()); disposable.add(rosterRepository.deleteContact(account.getId(), bareJid.asEntityBareJidOrThrow()) .subscribe( () -> LOGGER.log(Level.FINE, "Deletion of contact " + bareJid.toString() + " successful"), error -> LOGGER.log(Level.WARNING, "An error occurred deleting contact " + bareJid.toString(), error) )); disposable.add(rosterRepository.updateRosterVersion(account, version) .subscribe( success -> LOGGER.log(Level.FINE, "Upserted roster version to " + rosterVersion + " successfully"), error -> LOGGER.log(Level.WARNING, "An error occurred upserting roster version", error) )); return true; } @Override public void resetStore() { LOGGER.log(Level.INFO, "Reset Store"); disposable.add(rosterRepository.deleteAllContactsOfAccount(account) .subscribe( success -> LOGGER.log(Level.FINE, "Successfully reset store."), error -> LOGGER.log(Level.WARNING, "An error occurred resetting store", error) )); disposable.add(rosterRepository.updateRosterVersion(account, "") .subscribe( success -> LOGGER.log(Level.FINE, "Successfully reset roster version"), error -> LOGGER.log(Level.WARNING, "An error occurred resetting roster version", error) )); } public RosterPacket.Item fromModel(ContactModel contactModel) { RosterPacket.Item item = new RosterPacket.Item( contactModel.getEntity().getJid(), contactModel.getRostername()); if (contactModel.getSub_direction() != null) { item.setItemType(convert(contactModel.getSub_direction())); } item.setApproved(contactModel.isSub_approved()); item.setSubscriptionPending(contactModel.isSub_pending()); return item; } public ContactModel toModel(RosterPacket.Item item) { ContactModel contact = new ContactModel(); contact.setRostername(item.getName()); if (item.getItemType() != null) { contact.setSub_direction(convert(item.getItemType())); } contact.setSub_approved(item.isApproved()); contact.setSub_pending(item.isSubscriptionPending()); EntityModel entity = new EntityModel(); entity.setAccount(account); entity.setJid(item.getJid().asEntityBareJidOrThrow()); contact.setEntity(entity); return contact; } public SubscriptionDirection convert(RosterPacket.ItemType type) { return SubscriptionDirection.valueOf(type.toString()); } public RosterPacket.ItemType convert(SubscriptionDirection direction) { return RosterPacket.ItemType.fromString(direction.toString()); } }