Delete old core-old module

This commit is contained in:
Paul Schaub 2020-06-09 20:48:09 +02:00
parent fc754aa076
commit 5e47b39c5e
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
15 changed files with 0 additions and 1131 deletions

1
core-old/.gitignore vendored
View File

@ -1 +0,0 @@
/build

View File

@ -1,44 +0,0 @@
apply plugin: 'java-library'
// Add the generated folder to the source directories so that we can work with generated classes
// This is apparently necessary for use with requery.
sourceSets {
main.java.srcDirs += "${buildDir}/generated/sources/annotationProcessor/java/main/"
}
dependencies {
api project(':entity')
// Smack
// Not all of those are needed, but it may be a good idea to define those versions explicitly
api "org.igniterealtime.smack:smack-core:$smackCoreVersion"
api "org.igniterealtime.smack:smack-experimental:$smackExperimentalVersion"
api "org.igniterealtime.smack:smack-extensions:$smackExtensionsVersion"
api "org.igniterealtime.smack:smack-im:$smackImVersion"
api "org.igniterealtime.smack:smack-tcp:$smackTcpVersion"
// api "org.igniterealtime.smack:smack-omemo:$smackOmemoVersion"
// api "org.igniterealtime.smack:smack-omemo-signal:$smackOmemoSignalVersion"
// api "org.igniterealtime.smack:smack-openpgp:$smackOpenpgpVersion"
// api "org.igniterealtime.smack:smack-resolver-minidns:$smackResolverMiniDnsVersion"
// RxJava2
api "io.reactivex.rxjava2:rxjava:$rxJava2Version"
// Dagger 2 for dependency injection
implementation "com.google.dagger:dagger:$daggerVersion"
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
// Requery ORM
api "io.requery:requery:$requeryVersion"
annotationProcessor "io.requery:requery-processor:$requeryVersion"
// JUnit for testing
testImplementation "junit:junit:$junitVersion"
compile project(path: ':data')
}
sourceCompatibility = "8"
targetCompatibility = "8"

View File

@ -1,18 +0,0 @@
package org.mercury_im.domain.data.util;
import org.mercury_im.messenger.entity.contact.Peer;
public class ContactNameUtil {
public static String displayableNameFrom(Peer contact) {
if (contact == null) {
return null;
}
if (contact.getName() != null) {
return contact.getName();
}
return contact.getAddress();
}
}

View File

@ -1,10 +0,0 @@
package org.mercury_im.messenger.core;
import org.mercury_im.messenger.xmpp.util.ChatAndPossiblyContact;
public interface NotificationManager {
int chatMessageReceived(ChatAndPossiblyContact chatAndPossiblyContact, String body);
}

View File

@ -1,245 +0,0 @@
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<Long, MercuryConnection> 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<Long> 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 <a href="https://xmpp.org/extensions/xep-0352.html">XEP-0352: Client State Indication</a>
*/
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 <a href="https://xmpp.org/extensions/xep-0352.html">XEP-0352: Client State Indication</a>
*/
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()
//}
}
}

View File

@ -1,5 +0,0 @@
package org.mercury_im.messenger.core.centers;
public class ContactCenter {
}

View File

@ -1,13 +0,0 @@
package org.mercury_im.messenger.core.centers;
import javax.inject.Inject;
public class MessageCenter {
private final ConnectionCenter connectionCenter;
@Inject
public MessageCenter(ConnectionCenter connectionCenter) {
this.connectionCenter = connectionCenter;
}
}

View File

@ -1,48 +0,0 @@
package org.mercury_im.messenger.core.connection;
/**
* {@link MercuryConnection} modeled as a finite state machine.
* Below enums represent the states of the machine.
*/
public enum ConnectionState {
/**
* Connection is disconnected.
* This is the initial state of the machine.
*/
DISCONNECTED,
/**
* The connection is in the process of connecting to the server.
* This state can be reached by issuing a connect() call from within the {@link #DISCONNECTED}
* state.
*/
CONNECTING,
/**
* The connection is successfully connected to the server and the stream has been initiated.
*/
CONNECTED,
/**
* THe connection is authenticated.
* In this state the connection is ready to send and receive stanzas.
*/
AUTHENTICATED,
/**
* The connection is in the process of shutting down.
*/
DISCONNECTING,
/**
* The device doesn't have usable network connectivity.
*/
WAITING_FOR_NETWORK,
/**
* The connection already (unsuccessfully) tried to connect, but failed due to lack of network
* connectivity and is now waiting to retry connecting.
*/
WAITING_FOR_RETRY
}

View File

@ -1,32 +0,0 @@
package org.mercury_im.messenger.core.connection;
import org.jivesoftware.smack.SmackConfiguration;
public class MercuryConfiguration {
static {
SmackConfiguration.DEBUG = true;
// Make sure Smack is initialized.
SmackConfiguration.getVersion();
}
public static final String[] enabledCiphers = {
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA"
};
public static final String[] enabledProtocols = {
"TLSv1.2",
"TLSv1.3"
};
}

View File

@ -1,139 +0,0 @@
package org.mercury_im.messenger.core.connection;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.ReconnectionManager;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.roster.Roster;
import org.jivesoftware.smackx.carbons.CarbonManager;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.smackx.iqversion.VersionManager;
import org.jivesoftware.smackx.mam.MamManager;
import org.jivesoftware.smackx.sid.StableUniqueStanzaIdManager;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import io.reactivex.subjects.BehaviorSubject;
public class MercuryConnection {
public static final String TAG = "Mercury";
private static final Logger LOGGER = Logger.getLogger(MercuryConnection.class.getName());
private final AbstractXMPPConnection mConnection;
private final long mAccountId;
// Managers
private final ReconnectionManager mReconnectionManager;
private final Roster mRoster;
private final ChatManager mChatManager;
private final CarbonManager mCarbonManager;
private final StableUniqueStanzaIdManager mStanzaIdManager;
private final ServiceDiscoveryManager mServiceDiscoveryManager;
private final MamManager mMamManager;
private final VersionManager mVersionManager;
private BehaviorSubject<ConnectionState> mState = BehaviorSubject.createDefault(ConnectionState.DISCONNECTED);
public MercuryConnection(AbstractXMPPConnection connection, long accountId) {
mConnection = connection;
mConnection.addConnectionListener(mConnectionListener);
mAccountId = accountId;
mReconnectionManager = ReconnectionManager.getInstanceFor(connection);
mReconnectionManager.enableAutomaticReconnection();
mReconnectionManager.abortPossiblyRunningReconnection();
mRoster = Roster.getInstanceFor(connection);
mRoster.setRosterLoadedAtLogin(true);
mChatManager = ChatManager.getInstanceFor(connection);
mCarbonManager = CarbonManager.getInstanceFor(connection);
mStanzaIdManager = StableUniqueStanzaIdManager.getInstanceFor(connection);
mStanzaIdManager.enable();
mServiceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection);
mServiceDiscoveryManager.setIdentity(new DiscoverInfo.Identity("client", "Mercury", "phone"));
mVersionManager = VersionManager.getInstanceFor(connection);
mVersionManager.setVersion("Mercury", "0.0.1-stealth", "Android");
VersionManager.setAutoAppendSmackVersion(false);
mMamManager = MamManager.getInstanceFor(connection);
}
public void connect() throws InterruptedException, XMPPException, SmackException, IOException {
LOGGER.log(Level.INFO, "Connecting...");
mState.onNext(ConnectionState.CONNECTING);
AbstractXMPPConnection con = (AbstractXMPPConnection) getConnection();
con.connect().login();
}
public void disconnect() throws SmackException.NotConnectedException {
AbstractXMPPConnection con = (AbstractXMPPConnection) getConnection();
mState.onNext(ConnectionState.DISCONNECTING);
con.disconnect(new Presence(Presence.Type.unavailable));
mState.onNext(ConnectionState.DISCONNECTED);
}
public XMPPConnection getConnection() {
return mConnection;
}
public long getAccountId() {
return mAccountId;
}
public Roster getRoster() {
return mRoster;
}
private final ConnectionListener mConnectionListener = new ConnectionListener() {
@Override
public void connected(XMPPConnection connection) {
mState.onNext(ConnectionState.CONNECTED);
}
@Override
public void authenticated(XMPPConnection connection, boolean resumed) {
mState.onNext(ConnectionState.AUTHENTICATED);
LOGGER.info("Connection " + getAccountId() + " authenticated (" + (resumed ? "resumed" : "initially") + ")");
if (!resumed) {
LOGGER.info("Enabling carbons!");
mCarbonManager.enableCarbonsAsync(exception -> {
LOGGER.severe("Could not enable carbons for connection " + mAccountId + ".");
exception.printStackTrace();
});
}
}
@Override
public void connectionClosed() {
mState.onNext(ConnectionState.DISCONNECTED);
}
@Override
public void connectionClosedOnError(Exception e) {
mState.onNext(ConnectionState.DISCONNECTED);
}
};
public BehaviorSubject<ConnectionState> getState() {
return mState;
}
}

View File

@ -1,40 +0,0 @@
package org.mercury_im.messenger.core.di;
import org.mercury_im.messenger.core.NotificationManager;
import org.mercury_im.messenger.core.centers.ConnectionCenter;
import org.mercury_im.messenger.core.stores.EntityCapsStore;
import org.mercury_im.messenger.core.stores.PlainMessageStore;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class CenterModule {
@Singleton
@Provides
static ConnectionCenter provideConnectionCenter(EntityCapsStore capsStore,
PlainMessageStore messageStore,
AccountRepository accountRepository,
RosterRepository rosterRepository) {
return new ConnectionCenter(capsStore, messageStore, accountRepository, rosterRepository);
}
@Singleton
@Provides
static EntityCapsStore providerEntityCapsStore(EntityCapsRepository entityCapsRepository) {
return new EntityCapsStore(entityCapsRepository);
}
@Singleton
@Provides
static PlainMessageStore provideMessageStore(RosterRepository rosterRepository,
ChatRepository chatRepository,
MessageRepository messageRepository,
NotificationManager notificationManager) {
return new PlainMessageStore(rosterRepository, chatRepository, messageRepository, notificationManager);
}
}

View File

@ -1,16 +0,0 @@
package org.mercury_im.messenger.core.di;
import org.mercury_im.messenger.xmpp.di.RequeryModule;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
CenterModule.class,
RequeryModule.class
})
public interface XmppComponent {
}

View File

@ -1,101 +0,0 @@
package org.mercury_im.messenger.core.stores;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smackx.caps.cache.EntityCapsPersistentCache;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.mercury_im.messenger.data.model.EntityCapsModel;
import org.mercury_im.messenger.data.repository.XmppEntityCapsRepository;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import io.reactivex.disposables.CompositeDisposable;
public class EntityCapsStore implements EntityCapsPersistentCache {
private static final Logger LOGGER = Logger.getLogger(EntityCapsStore.class.getName());
private final XmppEntityCapsRepository entityCapsRepository;
private final Map<String, DiscoverInfo> discoverInfoMap = new HashMap<>();
private final CompositeDisposable disposable = new CompositeDisposable();
@Inject
public EntityCapsStore(XmppEntityCapsRepository entityCapsRepository) {
this.entityCapsRepository = entityCapsRepository;
populateFromDatabase();
}
/*
* Since nodeVers are - if ever - only deleted all at once but added one by one and never
* modified, we can simply determine the set of newly added nodeVers, process those and add
* them to the database.
*/
private void populateFromDatabase() {
disposable.add(entityCapsRepository.getAll()
.subscribe(
entityCapsModels -> {
Map<String, EntityCapsModel> nextEntityCaps = entityCapsModels.toMap(EntityCapsModel.NODE_VER);
// New set of nodeVers
Set<String> nextKeys = nextEntityCaps.keySet();
// Old set of nodeVers
Set<String> previousKeys = discoverInfoMap.keySet();
// Added nodeVers
nextKeys.removeAll(previousKeys);
for (String key : nextKeys) {
// Only add new items. Items itself cannot change, so we don't have to deal
// with changed items.
EntityCapsModel addedModel = nextEntityCaps.get(key);
DiscoverInfo info;
try {
XmlPullParser parser = PacketParserUtils.getParserFor(new StringReader(addedModel.getXml()));
info = (DiscoverInfo) PacketParserUtils.parseIQ(parser);
discoverInfoMap.put(addedModel.getNodeVer(), info);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error parsing EntityCaps: ", e);
}
}
},
error -> LOGGER.log(Level.WARNING, "An error occurred while updating the EntityCaps cache.", error)));
}
@Override
public void addDiscoverInfoByNodePersistent(String nodeVer, DiscoverInfo info) {
EntityCapsModel model = new EntityCapsModel();
model.setNodeVer(nodeVer);
CharSequence xml = info.toXML();
String string = xml.toString();
model.setXml(string);
disposable.add(entityCapsRepository.upsert(model).subscribe(
success -> LOGGER.log(Level.FINE, "Upserted EntityCaps model " + success),
error -> LOGGER.log(Level.WARNING, "An error occurred upserting EntityCaps model", error)
));
}
@Override
public DiscoverInfo lookup(String nodeVer) {
LOGGER.log(Level.FINE, "Looking up caps for " + nodeVer + " in cache...");
DiscoverInfo info = discoverInfoMap.get(nodeVer);
LOGGER.log(Level.FINE, "Entry found: " + (info != null ? info.toXML().toString() : "null"));
return info;
}
@Override
public void emptyCache() {
disposable.add(entityCapsRepository.deleteAll().subscribe(
success -> LOGGER.log(Level.FINE, "EntityCaps table cleared successfully."),
error -> LOGGER.log(Level.WARNING, "An error occurred while clearing EntityCaps table.", error)
));
}
}

View File

@ -1,202 +0,0 @@
package org.mercury_im.messenger.core.stores;
import org.jivesoftware.smack.chat2.Chat;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.carbons.CarbonManager;
import org.jivesoftware.smackx.carbons.packet.CarbonExtension;
import org.jivesoftware.smackx.delay.DelayInformationManager;
import org.jivesoftware.smackx.delay.packet.DelayInformation;
import org.jivesoftware.smackx.mam.MamManager;
import org.jivesoftware.smackx.sid.element.OriginIdElement;
import org.jivesoftware.smackx.sid.element.StanzaIdElement;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.mercury_im.messenger.core.NotificationManager;
import org.mercury_im.messenger.core.connection.MercuryConnection;
import org.mercury_im.messenger.xmpp.model.ChatModel;
import org.mercury_im.messenger.xmpp.model.ContactModel;
import org.mercury_im.messenger.xmpp.model.EntityModel;
import org.mercury_im.messenger.xmpp.model.LastChatMessageRelation;
import org.mercury_im.messenger.xmpp.model.MessageModel;
import org.mercury_im.messenger.xmpp.repository.ChatRepository;
import org.mercury_im.messenger.xmpp.repository.MessageRepository;
import org.mercury_im.messenger.xmpp.repository.RosterRepository;
import org.mercury_im.messenger.xmpp.util.ChatAndPossiblyContact;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import io.reactivex.Completable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
public class PlainMessageStore {
private static final Logger LOGGER = Logger.getLogger(PlainMessageStore.class.getName());
private static final CompositeDisposable disposable = new CompositeDisposable();
private final RosterRepository rosterRepository;
private final ChatRepository chatRepository;
private final MessageRepository messageRepository;
private final NotificationManager notificationManager;
public PlainMessageStore(RosterRepository rosterRepository, ChatRepository chatRepository, MessageRepository messageRepository, NotificationManager notificationManager) {
this.rosterRepository = rosterRepository;
this.chatRepository = chatRepository;
this.messageRepository = messageRepository;
this.notificationManager = notificationManager;
}
public void handleIncomingMessage(long accountId, EntityBareJid from, Message message, Chat chat) {
if (message.getBody() == null) {
return;
}
Completable.fromAction(() -> {
EntityModel entityModel = rosterRepository.getOrCreateEntity(accountId, from)
.blockingGet();
ContactModel contactModel = rosterRepository.getContact(accountId, entityModel.getJid()).blockingFirst().firstOrNull();
ChatModel chatModel = chatRepository.getChatWith(entityModel).blockingFirst().firstOr(() -> {
ChatModel freshChatModel = new ChatModel();
freshChatModel.setPeer(entityModel);
freshChatModel.setDisplayed(true);
return freshChatModel;
});
chatModel = chatRepository.upsert(chatModel).blockingGet();
MessageModel messageModel = setCommonMessageAttributes(message, chatModel);
messageModel.setSender(from);
messageModel.setIncoming(true);
final ChatModel fChatModel = chatModel;
disposable.add(messageRepository.insert(messageModel)
.subscribe(insertedMessageModel -> {
if (message.getBody() != null) {
notificationManager.chatMessageReceived(new ChatAndPossiblyContact(fChatModel, contactModel), message.getBody());
}
LastChatMessageRelation lastMessage = new LastChatMessageRelation();
lastMessage.setChat(fChatModel);
lastMessage.setMessage(insertedMessageModel);
}));
}).subscribeOn(Schedulers.io())
.subscribe();
}
public void handleOutgoingMessage(long accountId, EntityBareJid to, Message message, Chat chat) {
MessageModel model = setCommonMessageAttributes(message, null);
EntityModel entityModel = rosterRepository.getOrCreateEntity(accountId, to).blockingGet();
model.setIncoming(false);
model.setTimestamp(new Date());
model.setSender(entityModel.getAccount().getJid());
model.setRecipient(to);
ChatModel chatModel = chatRepository.getChatWith(entityModel).blockingFirst().firstOr(() -> {
ChatModel freshChatModel = new ChatModel();
freshChatModel.setPeer(entityModel);
freshChatModel.setDisplayed(true);
return freshChatModel;
});
model.setChat(chatModel);
disposable.add(messageRepository.upsert(model)
.subscribe(messageId ->
LOGGER.log(Level.INFO, "Inserted outgoing message " + messageId)));
}
public void handleCarbonCopy(long accountId, CarbonExtension.Direction direction, Message carbonCopy, Message wrappingMessage) {
if (carbonCopy.getBody() == null) {
return;
}
MessageModel messageModel = new MessageModel();
messageModel.setSender(carbonCopy.getFrom() != null ? carbonCopy.getFrom().asEntityBareJidIfPossible() : null);
messageModel.setRecipient(carbonCopy.getTo() != null ? carbonCopy.getTo().asEntityBareJidIfPossible() : null);
messageModel.setIncoming(direction == CarbonExtension.Direction.received);
messageModel.setBody(carbonCopy.getBody());
messageModel.setTimestamp(new Date());
disposable.add(messageRepository.upsert(messageModel)
.subscribe(messageId ->
LOGGER.log(Level.INFO, "Inserted carbon message " + messageId)));
}
public void registerForMercuryConnection(MercuryConnection connection) {
ChatManager chatManager = ChatManager.getInstanceFor(connection.getConnection());
CarbonManager carbonManager = CarbonManager.getInstanceFor(connection.getConnection());
// Add account ID to
chatManager.addIncomingListener((from, message, chat) ->
PlainMessageStore.this.handleIncomingMessage(
connection.getAccountId(), from, message, chat));
chatManager.addOutgoingListener((to, message, chat) ->
PlainMessageStore.this.handleOutgoingMessage(
connection.getAccountId(), to, message, chat));
carbonManager.addCarbonCopyReceivedListener((direction, carbonCopy, wrappingMessage) ->
PlainMessageStore.this.handleCarbonCopy(
connection.getAccountId(), direction, carbonCopy, wrappingMessage));
}
public void dispose() {
disposable.clear();
}
public void handleMamResult(long accountId, EntityBareJid peerJid, MamManager.MamQuery query) {
List<MessageModel> messageModels = new ArrayList<>();
for (Message message : query.getMessages()) {
Date date = new Date();
DelayInformation delay = DelayInformation.from(message);
if (delay != null) {
date = delay.getStamp();
}
MessageModel messageModel = new MessageModel();
messageModel.setBody(message.getBody());
messageModel.setSender(message.getFrom().asEntityBareJidOrThrow());
messageModel.setRecipient(message.getTo().asEntityBareJidOrThrow());
messageModel.setIncoming(peerJid.equals(message.getFrom().asEntityBareJidOrThrow()));
messageModel.setTimestamp(date);
messageModels.add(messageModel);
}
disposable.add(messageRepository.upsert(messageModels).subscribe());
}
private MessageModel incomingMessageToModel(Message message, ChatModel chat) {
MessageModel model = setCommonMessageAttributes(message, chat);
model.setIncoming(true);
return model;
}
private MessageModel setCommonMessageAttributes(Message message, ChatModel chat) {
MessageModel model = new MessageModel();
model.setBody(message.getBody());
Date timestamp = DelayInformationManager.getDelayTimestamp(message);
model.setTimestamp(timestamp == null ? new Date() : timestamp);
model.setThread(message.getThread());
model.setLegacyId(message.getStanzaId());
model.setChat(chat);
model.setRecipient(message.getTo().asEntityBareJidOrThrow());
model.setSender(message.getFrom() != null ? message.getFrom().asEntityBareJidIfPossible() : null);
OriginIdElement originId = OriginIdElement.getOriginId(message);
model.setOriginId(originId != null ? originId.getId() : null);
StanzaIdElement stanzaId = StanzaIdElement.getStanzaId(message);
model.setStanzaId(stanzaId != null ? stanzaId.getId() : null);
model.setStanzaIdBy(stanzaId != null ? JidCreate.entityBareFromOrThrowUnchecked(stanzaId.getBy()) : null);
return model;
}
}

View File

@ -1,217 +0,0 @@
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<Jid, RosterPacket.Item> 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<RosterPacket.Item> 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<RosterPacket.Item> 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());
}
}