package org.mercury_im.messenger.core; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.chat2.ChatManager; import org.jivesoftware.smack.roster.Roster; import org.jivesoftware.smack.roster.RosterEntry; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.stringprep.XmppStringprepException; import org.mercury_im.messenger.core.data.repository.Repositories; import org.mercury_im.messenger.core.exception.ConnectionNotFoundException; import org.mercury_im.messenger.core.exception.ContactAlreadyAddedException; import org.mercury_im.messenger.core.xmpp.MercuryConnection; import org.mercury_im.messenger.core.xmpp.MercuryConnectionManager; import org.mercury_im.messenger.entity.contact.Peer; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; import javax.inject.Inject; import javax.inject.Singleton; import io.reactivex.Completable; import io.reactivex.disposables.CompositeDisposable; @Singleton public class Messenger { public static final String TAG = "MercuryIM"; private static final Logger LOGGER = Logger.getLogger(Messenger.class.getName()); private final MercuryConnectionManager connectionManager; private final Repositories repositories; private final SchedulersFacade schedulers; private CompositeDisposable disposable = new CompositeDisposable(); @Inject public Messenger(Repositories repositories, MercuryConnectionManager connectionManager, SchedulersFacade schedulers) { this.repositories = repositories; this.connectionManager = connectionManager; this.schedulers = schedulers; performInitialLogin(); } public MercuryConnectionManager getConnectionManager() { return connectionManager; } public void performInitialLogin() { LOGGER.log(Level.INFO, "Perform initial login."); disposable.add(repositories.getAccountRepository().observeAllAccounts().firstOrError() .subscribeOn(schedulers.getIoScheduler()) .subscribe(connectionManager::doRegisterConnections)); } public Completable addContact(UUID accountId, String contactAddress) { return Completable.fromAction(() -> doAddContact(accountId, contactAddress)); } private void doAddContact(UUID accountId, String contactAddress) throws ConnectionNotFoundException, XmppStringprepException, ContactAlreadyAddedException, SmackException.NotLoggedInException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { MercuryConnection connection = getConnection(accountId); EntityBareJid jid = JidCreate.entityBareFrom(contactAddress); Roster roster = Roster.getInstanceFor(connection.getConnection()); if (roster.getEntry(jid) != null) { throw new ContactAlreadyAddedException(jid); } if (roster.isSubscriptionPreApprovalSupported()) { LOGGER.log(Level.INFO, "Pre-Approval supported."); try { roster.preApproveAndCreateEntry(jid, null, null); } catch (SmackException.FeatureNotSupportedException e) { throw new AssertionError("pre-approval failed even though the feature is announced."); } } else { roster.createItemAndRequestSubscription(jid, null, null); } } public Completable deleteContact(Peer contact) { return Completable.fromAction(() -> doDeleteContact(contact)); } private void doDeleteContact(Peer contact) throws ConnectionNotFoundException, XmppStringprepException, SmackException.NotLoggedInException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { MercuryConnection connection = getConnection(contact.getAccount().getId()); Roster roster = Roster.getInstanceFor(connection.getConnection()); EntityBareJid jid = JidCreate.entityBareFrom(contact.getAddress()); RosterEntry entry = roster.getEntry(jid); if (entry != null) { roster.removeEntry(entry); } else { throw new IllegalStateException("Contact " + jid.toString() + " not in roster!"); } } public Completable sendMessage(Peer peer, String body) { return Completable.fromAction(() -> doSendMessage(peer, body)); } private void doSendMessage(Peer contact, String body) throws ConnectionNotFoundException, SmackException.NotConnectedException, InterruptedException { MercuryConnection connection = getConnection(contact.getAccount().getId()); ChatManager chatManager = ChatManager.getInstanceFor(connection.getConnection()); chatManager.chatWith(JidCreate.entityBareFromOrThrowUnchecked(contact.getAddress())) .send(body); } private MercuryConnection getConnection(UUID accountId) throws ConnectionNotFoundException { MercuryConnection connection = getConnectionManager().getConnection(accountId); if (connection == null) { throw new ConnectionNotFoundException(accountId); } return connection; } }