package org.mercury_im.messenger.core.centers; import org.jivesoftware.smack.AbstractXMPPConnection; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smackx.caps.EntityCapsManager; import org.jivesoftware.smackx.csi.ClientStateIndicationManager; import org.jivesoftware.smackx.mam.MamManager; import org.mercury_im.messenger.core.connection.MercuryConfiguration; import org.mercury_im.messenger.core.connection.MercuryConnection; import org.mercury_im.messenger.xmpp.model.AccountModel; import org.mercury_im.messenger.xmpp.model.ChatModel; import org.mercury_im.messenger.xmpp.repository.RequeryAccountRepository; import org.mercury_im.messenger.xmpp.repository.RosterRepository; import org.mercury_im.messenger.core.stores.EntityCapsStore; import org.mercury_im.messenger.core.stores.PlainMessageStore; import org.mercury_im.messenger.core.stores.RosterStore; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; import javax.inject.Inject; import javax.inject.Singleton; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; @Singleton public class ConnectionCenter { private static final Logger LOGGER = Logger.getLogger(ConnectionCenter.class.getName()); // Injected private final RequeryAccountRepository accountRepository; private final RosterRepository rosterRepository; private final PlainMessageStore messageStore; private final EntityCapsStore entityCapsStore; // Connections private final Map connectionMap = Collections.synchronizedMap(new HashMap<>()); // Disposable for rx private final CompositeDisposable disposable = new CompositeDisposable(); private final AtomicBoolean isConnectionCenterStarted = new AtomicBoolean(false); @Inject public ConnectionCenter(EntityCapsStore capsStore, PlainMessageStore messageStore, RequeryAccountRepository accountRepository, RosterRepository rosterRepository) { LOGGER.log(Level.INFO, "ConnectionCenter initialized"); this.entityCapsStore = capsStore; this.messageStore = messageStore; this.accountRepository = accountRepository; this.rosterRepository = rosterRepository; EntityCapsManager.setPersistentCache(capsStore); startUp(); } /** * Start up the center by subscribing to changes of the {@link AccountModel accounts} in the * database. For each new {@link AccountModel} it creates a {@link MercuryConnection} and * stores it in the {@link #connectionMap}. */ @SuppressWarnings("unchecked") public synchronized void startUp() { if (isConnectionCenterStarted.getAndSet(true)) { // already started. return; } // otherwise subscribe to accounts and create connections. Disposable allAccounts = accountRepository.getAll() .observeOn(Schedulers.newThread()) .subscribe(accounts -> { LOGGER.log(Level.INFO, "Accounts changed."); Set accountIds = new HashSet<>(); // Add missing connections to the map for (AccountModel account : accounts.toList()) { accountIds.add(account.getId()); if (connectionMap.get(account.getId()) != null) { continue; } LOGGER.log(Level.INFO, "Add new connection " + account.getJid().toString() + " to ConnectionCenter list."); MercuryConnection connection = createConnection(account); connectionMap.put(account.getId(), connection); // initialize new connection initializeConnection(connection); } // Remove unwanted connections from the map for (long connectionId : connectionMap.keySet()) { if (!accountIds.contains(connectionId)) { LOGGER.log(Level.INFO, "Connection " + connectionId + " was deleted."); AbstractXMPPConnection con = (AbstractXMPPConnection) connectionMap.get(connectionId).getConnection(); con.disconnect(); connectionMap.remove(connectionId); } } for (AccountModel account : accounts) { MercuryConnection connection = connectionMap.get(account.getId()); if (account.isEnabled()) { if (connection.getConnection().isConnected()) { continue; } LOGGER.log(Level.INFO, "Connecting connection " + account.getId() + " (" + account.getJid().toString() + ")"); connection.connect(); LOGGER.log(Level.INFO, "Connected!"); } else { if (!connection.getConnection().isConnected()) { continue; } LOGGER.log(Level.INFO, "Account " + account.getJid() + " (" + account.getJid().toString() + ") not enabled. Disable."); connection.disconnect(); } } }); disposable.add(allAccounts); } public MercuryConnection getConnection(AccountModel account) { return getConnection(account.getId()); } public MercuryConnection getConnection(long accountId) { return connectionMap.get(accountId); } public void putConnection(MercuryConnection connection) { connectionMap.put(connection.getAccountId(), connection); } /** * Create a new {@link MercuryConnection} with an underlying {@link XMPPTCPConnection} * from the credentials of an {@link AccountModel}. * The new connection will not be connected. * * @param accountModel accountModel * * @return disconnected mercury connection */ public MercuryConnection createConnection(AccountModel accountModel) { LOGGER.log(Level.INFO, "Create Connection for " + accountModel.getJid().toString()); XMPPTCPConnectionConfiguration configuration = XMPPTCPConnectionConfiguration.builder() .setHost(accountModel.getJid().getDomain().toString()) .setXmppAddressAndPassword(accountModel.getJid(), accountModel.getPassword()) .setConnectTimeout(2 * 60 * 1000) .setEnabledSSLCiphers(MercuryConfiguration.enabledCiphers) .setEnabledSSLProtocols(MercuryConfiguration.enabledProtocols) .build(); AbstractXMPPConnection tcpConnection = new XMPPTCPConnection(configuration); return new MercuryConnection(tcpConnection, accountModel.getId()); } public void initializeConnection(MercuryConnection connection) { // Register roster store RosterStore rosterStore = new RosterStore(rosterRepository, accountRepository); rosterStore.setAccountId(connection.getAccountId()); rosterStore.subscribe(); connection.getRoster().setRosterStore(rosterStore); // Register message store messageStore.registerForMercuryConnection(connection); } /** * Set Client State Indication status to active. * * @see XEP-0352: Client State Indication */ public void clientStateActive() { LOGGER.log(Level.INFO, "CSI: App is going to foreground -> active"); for (MercuryConnection mercuryConnection : connectionMap.values()) { XMPPConnection connection = mercuryConnection.getConnection(); if (connection.isConnected() && ClientStateIndicationManager.isSupported(connection)) { try { ClientStateIndicationManager.active(mercuryConnection.getConnection()); } catch (SmackException.NotConnectedException | InterruptedException e) { e.printStackTrace(); } } } } /** * Set Client State Indication status to inactive. * * @see XEP-0352: Client State Indication */ public void clientStateInactive() { LOGGER.log(Level.INFO, "CSI: App is going to background -> inactive"); for (MercuryConnection mercuryConnection : connectionMap.values()) { XMPPConnection connection = mercuryConnection.getConnection(); if (connection.isConnected() && ClientStateIndicationManager.isSupported(connection)) { try { ClientStateIndicationManager.inactive(connection); } catch (SmackException.NotConnectedException | InterruptedException e) { e.printStackTrace(); } } } } public void requestMamMessagesFor(ChatModel chat) { MercuryConnection connection = connectionMap.get(chat.getPeer().getAccount().getId()); if (connection == null) return; MamManager mamManager = MamManager.getInstanceFor(connection.getConnection()); MamManager.MamQuery query; //if (chat.getEarliestMamMessageId() == null) { try { query = mamManager.queryMostRecentPage(chat.getPeer().getJid(), 100); messageStore.handleMamResult(chat.getPeer().getAccount().getId(), chat.getPeer().getJid(), query); } catch (SmackException.NoResponseException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NotLoggedInException | InterruptedException e) { e.printStackTrace(); } //} else { //MamManager.MamQueryArgs queryArgs = MamManager.MamQueryArgs.builder() // .beforeUid() // .build(); //query = mamManager.queryArchive() //} } }