Start work on new message sending logic

This commit is contained in:
Paul Schaub 2020-07-30 19:52:58 +02:00
parent 9ea08b11da
commit 0adbcc94b0
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
35 changed files with 517 additions and 142 deletions

View File

@ -8,6 +8,7 @@ import org.mercury_im.messenger.android.ui.roster.contacts.AndroidContactListVie
import org.mercury_im.messenger.core.di.module.OpenPgpModule; import org.mercury_im.messenger.core.di.module.OpenPgpModule;
import org.mercury_im.messenger.core.di.module.RxMercuryMessageStoreFactoryModule; import org.mercury_im.messenger.core.di.module.RxMercuryMessageStoreFactoryModule;
import org.mercury_im.messenger.core.di.module.RxMercuryRosterStoreFactoryModule; import org.mercury_im.messenger.core.di.module.RxMercuryRosterStoreFactoryModule;
import org.mercury_im.messenger.core.di.module.StanzaIdSourceFactoryModule;
import org.mercury_im.messenger.core.di.module.XmppTcpConnectionFactoryModule; import org.mercury_im.messenger.core.di.module.XmppTcpConnectionFactoryModule;
import org.mercury_im.messenger.core.viewmodel.account.detail.AccountDetailsViewModel; import org.mercury_im.messenger.core.viewmodel.account.detail.AccountDetailsViewModel;
import org.mercury_im.messenger.core.viewmodel.account.list.AccountListViewModel; import org.mercury_im.messenger.core.viewmodel.account.list.AccountListViewModel;
@ -48,7 +49,8 @@ import dagger.Component;
XmppTcpConnectionFactoryModule.class, XmppTcpConnectionFactoryModule.class,
RxMercuryMessageStoreFactoryModule.class, RxMercuryMessageStoreFactoryModule.class,
OpenPgpModule.class, OpenPgpModule.class,
RxMercuryRosterStoreFactoryModule.class RxMercuryRosterStoreFactoryModule.class,
StanzaIdSourceFactoryModule.class
}) })
public interface AppComponent { public interface AppComponent {

View File

@ -1,6 +1,5 @@
package org.mercury_im.messenger.data.converter; package org.mercury_im.messenger.data.converter;
import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.EntityJid; import org.jxmpp.jid.EntityJid;
import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.impl.JidCreate;
@ -27,18 +26,15 @@ public class EntityJidConverter implements Converter<EntityJid, String> {
@Override @Override
public String convertToPersisted(EntityJid value) { public String convertToPersisted(EntityJid value) {
if (value == null) return null;
return value.toString(); return value.toString();
} }
@Override @Override
public EntityJid convertToMapped(Class<? extends EntityJid> type, @Nullable String value) { public EntityJid convertToMapped(Class<? extends EntityJid> type, @Nullable String value) {
switch (type.getName()) { if (value == null) {
case "EntityFullJid": return null;
return JidCreate.entityFullFromOrThrowUnchecked(value);
case "EntityBareJid":
return JidCreate.entityBareFromOrThrowUnchecked(value);
default:
throw new IllegalArgumentException("Unknown jid type encountered: " + type.getName());
} }
return JidCreate.entityFromOrThrowUnchecked(value);
} }
} }

View File

@ -0,0 +1,37 @@
package org.mercury_im.messenger.data.mapping;
import org.jivesoftware.smackx.chat_markers.ChatMarkersState;
import org.mercury_im.messenger.entity.message.ChatMarkerState;
public class ChatMarkerMapping {
public static ChatMarkersState toSmackChatMarker(ChatMarkerState mercuryChatMarker) {
switch (mercuryChatMarker) {
case markable:
return ChatMarkersState.markable;
case received:
return ChatMarkersState.received;
case displayed:
return ChatMarkersState.displayed;
case acknowledged:
return ChatMarkersState.acknowledged;
default:
throw new IllegalArgumentException("Illegal ChatMarkersState: " + mercuryChatMarker);
}
}
public static ChatMarkerState toMercuryChatMarker(ChatMarkersState smackChatMarker) {
switch (smackChatMarker) {
case markable:
return ChatMarkerState.markable;
case received:
return ChatMarkerState.received;
case displayed:
return ChatMarkerState.displayed;
case acknowledged:
return ChatMarkerState.acknowledged;
default:
throw new IllegalArgumentException("Illegal ChatMarkersState: " + smackChatMarker);
}
}
}

View File

@ -26,6 +26,7 @@ public class MessageMapping extends AbstractMapping<Message, MessageModel> {
@Override @Override
public MessageModel mapToModel(Message entity, MessageModel model) { public MessageModel mapToModel(Message entity, MessageModel model) {
model.setId(entity.getId()); model.setId(entity.getId());
model.setChatId(entity.getChatId());
model.setSender(entity.getSender()); model.setSender(entity.getSender());
model.setRecipient(entity.getRecipient()); model.setRecipient(entity.getRecipient());
model.setTimestamp(entity.getTimestamp()); model.setTimestamp(entity.getTimestamp());
@ -42,6 +43,7 @@ public class MessageMapping extends AbstractMapping<Message, MessageModel> {
@Override @Override
public Message mapToEntity(MessageModel model, Message entity) { public Message mapToEntity(MessageModel model, Message entity) {
entity.setId(model.getId()); entity.setId(model.getId());
entity.setChatId(model.getChatId());
entity.setSender(model.getSender()); entity.setSender(model.getSender());
entity.setRecipient(model.getRecipient()); entity.setRecipient(model.getRecipient());
entity.setTimestamp(model.getTimestamp()); entity.setTimestamp(model.getTimestamp());

View File

@ -1,9 +1,11 @@
package org.mercury_im.messenger.data.model; package org.mercury_im.messenger.data.model;
import org.jxmpp.jid.EntityFullJid; import org.jxmpp.jid.EntityJid;
import org.mercury_im.messenger.data.converter.EntityFullJidConverter; import org.mercury_im.messenger.data.converter.EntityJidConverter;
import org.mercury_im.messenger.data.converter.MessageDirectionConverter; import org.mercury_im.messenger.data.converter.MessageDirectionConverter;
import org.mercury_im.messenger.entity.Encryption; import org.mercury_im.messenger.entity.Encryption;
import org.mercury_im.messenger.entity.message.ChatMarkerState;
import org.mercury_im.messenger.entity.message.MessageDeliveryState;
import org.mercury_im.messenger.entity.message.MessageDirection; import org.mercury_im.messenger.entity.message.MessageDirection;
import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.OpenPgpV4Fingerprint;
@ -27,15 +29,16 @@ public abstract class AbstractMessageModel implements Persistable {
UUID id; UUID id;
@Column(nullable = false) @Column(nullable = false)
@Convert(UUIDConverter.class)
UUID chatId; UUID chatId;
@Column(nullable = false) @Column(nullable = false)
@Convert(EntityFullJidConverter.class) @Convert(EntityJidConverter.class)
EntityFullJid sender; EntityJid sender;
@Column(nullable = false) @Column(nullable = false)
@Convert(EntityFullJidConverter.class) @Convert(EntityJidConverter.class)
EntityFullJid recipient; EntityJid recipient;
@Column(name = "\"timestamp\"", nullable = false) @Column(name = "\"timestamp\"", nullable = false)
Date timestamp; Date timestamp;
@ -68,4 +71,10 @@ public abstract class AbstractMessageModel implements Persistable {
@Column @Column
OpenPgpV4Fingerprint senderOXFingerprint; OpenPgpV4Fingerprint senderOXFingerprint;
@Column
ChatMarkerState chatMarkerState;
@Column
MessageDeliveryState deliveryState;
} }

View File

@ -1,5 +1,6 @@
package org.mercury_im.messenger.data.repository; package org.mercury_im.messenger.data.repository;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.core.data.repository.MessageRepository; import org.mercury_im.messenger.core.data.repository.MessageRepository;
import org.mercury_im.messenger.data.mapping.DirectChatMapping; import org.mercury_im.messenger.data.mapping.DirectChatMapping;
import org.mercury_im.messenger.data.mapping.GroupChatMapping; import org.mercury_im.messenger.data.mapping.GroupChatMapping;
@ -10,7 +11,9 @@ import org.mercury_im.messenger.data.repository.dao.GroupChatDao;
import org.mercury_im.messenger.data.repository.dao.MessageDao; import org.mercury_im.messenger.data.repository.dao.MessageDao;
import org.mercury_im.messenger.entity.chat.DirectChat; import org.mercury_im.messenger.entity.chat.DirectChat;
import org.mercury_im.messenger.entity.chat.GroupChat; import org.mercury_im.messenger.entity.chat.GroupChat;
import org.mercury_im.messenger.entity.message.ChatMarkerState;
import org.mercury_im.messenger.entity.message.Message; import org.mercury_im.messenger.entity.message.Message;
import org.mercury_im.messenger.entity.message.MessageDeliveryState;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -105,7 +108,7 @@ public class RxMessageRepository
.from(MessageModel.class) .from(MessageModel.class)
.where(MessageModel.BODY.like("%" + body + "%") .where(MessageModel.BODY.like("%" + body + "%")
.and(MessageModel.CHAT_ID.eq(chat.getId()))) .and(MessageModel.CHAT_ID.eq(chat.getId())))
.get().observableResult() .get().observableResult()
.map(ResultDelegate::toList) .map(ResultDelegate::toList)
.map(this::messageModelsToEntities); .map(this::messageModelsToEntities);
@ -117,7 +120,7 @@ public class RxMessageRepository
.from(MessageModel.class) .from(MessageModel.class)
.where(MessageModel.BODY.like("%" + body + "%") .where(MessageModel.BODY.like("%" + body + "%")
.and(MessageModel.CHAT_ID.eq(chat.getId()))) .and(MessageModel.CHAT_ID.eq(chat.getId())))
.get().observableResult() .get().observableResult()
.map(ResultDelegate::toList) .map(ResultDelegate::toList)
.map(this::messageModelsToEntities); .map(this::messageModelsToEntities);
@ -146,6 +149,24 @@ public class RxMessageRepository
.get().single().ignoreElement(); .get().single().ignoreElement();
} }
@Override
public Completable markMessage(String stanzaId, EntityBareJid recipientJid, ChatMarkerState state) {
return data().update(MessageModel.class)
.set(MessageModel.CHAT_MARKER_STATE, state)
.where(MessageModel.LEGACY_ID.eq(stanzaId)
.and(MessageModel.RECIPIENT.eq(recipientJid)))
.get().single().ignoreElement();
}
@Override
public Completable updateDeliveryState(UUID messageId, MessageDeliveryState deliveryState) {
return data().update(MessageModel.class)
.set(MessageModel.DELIVERY_STATE, deliveryState)
.where(MessageModel.ID.eq(messageId))
.get().single()
.ignoreElement();
}
private List<Message> messageModelsToEntities(List<MessageModel> models) { private List<Message> messageModelsToEntities(List<MessageModel> models) {
List<Message> entities = new ArrayList<>(models.size()); List<Message> entities = new ArrayList<>(models.size());
for (MessageModel model : models) { for (MessageModel model : models) {

View File

@ -1,5 +1,10 @@
apply plugin: 'java-library' apply plugin: 'java-library'
sourceSets {
//main.java.srcDirs += "${buildDir}/generated/sources/annotationProcessor/java/main/"
test.java.srcDirs += "${buildDir}/generated/sources/annotationProcessor/java/test/"
}
dependencies { dependencies {
implementation project(':entity') implementation project(':entity')
@ -20,7 +25,9 @@ dependencies {
// Dagger 2 for dependency injection // Dagger 2 for dependency injection
implementation "com.google.dagger:dagger:$daggerVersion" implementation "com.google.dagger:dagger:$daggerVersion"
testImplementation "com.google.dagger:dagger:$daggerVersion"
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion" annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
testAnnotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
compileOnly "org.projectlombok:lombok:$lombokVersion" compileOnly "org.projectlombok:lombok:$lombokVersion"
annotationProcessor "org.projectlombok:lombok:$lombokVersion" annotationProcessor "org.projectlombok:lombok:$lombokVersion"

View File

@ -13,8 +13,13 @@ import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException; import org.jxmpp.stringprep.XmppStringprepException;
import org.mercury_im.messenger.core.connection.MercuryConnection; import org.mercury_im.messenger.core.connection.MercuryConnection;
import org.mercury_im.messenger.core.connection.MercuryConnectionManager; import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
import org.mercury_im.messenger.core.connection.message.MessageConsignor;
import org.mercury_im.messenger.core.connection.message.OxMessageConsignor;
import org.mercury_im.messenger.core.connection.message.PlaintextMessageConsignor;
import org.mercury_im.messenger.core.data.repository.MessageRepository;
import org.mercury_im.messenger.core.exception.ConnectionNotFoundException; import org.mercury_im.messenger.core.exception.ConnectionNotFoundException;
import org.mercury_im.messenger.core.exception.ContactAlreadyAddedException; import org.mercury_im.messenger.core.exception.ContactAlreadyAddedException;
import org.mercury_im.messenger.entity.chat.Chat;
import org.mercury_im.messenger.entity.contact.Peer; import org.mercury_im.messenger.entity.contact.Peer;
import java.io.IOException; import java.io.IOException;
@ -34,12 +39,15 @@ public class Messenger {
private static final Logger LOGGER = Logger.getLogger(Messenger.class.getName()); private static final Logger LOGGER = Logger.getLogger(Messenger.class.getName());
private final MercuryConnectionManager connectionManager; private final MercuryConnectionManager connectionManager;
private final MessageRepository messageRepository;
private final SchedulersFacade schedulers; private final SchedulersFacade schedulers;
@Inject @Inject
public Messenger(MercuryConnectionManager connectionManager, public Messenger(MercuryConnectionManager connectionManager,
MessageRepository messageRepository,
SchedulersFacade schedulers) { SchedulersFacade schedulers) {
this.connectionManager = connectionManager; this.connectionManager = connectionManager;
this.messageRepository = messageRepository;
this.schedulers = schedulers; this.schedulers = schedulers;
} }
@ -127,4 +135,16 @@ public class Messenger {
} }
return connection; return connection;
} }
public MessageConsignor getMessageConsignor(Chat chat) {
switch (chat.getChatPreferences().getEncryption()) {
case plain:
return new PlaintextMessageConsignor(connectionManager, messageRepository, chat);
case ox_sign:
case ox_crypt:
case ox_signcrypt:
return new OxMessageConsignor(connectionManager, messageRepository, chat);
}
throw new AssertionError("Illegal Encryption Type: " + chat.getChatPreferences().getEncryption());
}
} }

View File

@ -159,7 +159,6 @@ public class MercuryConnectionManager {
MercuryMessageStore mercuryMessageStore = messageStoreFactory.createMessageStore(account); MercuryMessageStore mercuryMessageStore = messageStoreFactory.createMessageStore(account);
ChatManager chatManager = ChatManager.getInstanceFor(connection.getConnection()); ChatManager chatManager = ChatManager.getInstanceFor(connection.getConnection());
chatManager.addIncomingListener(mercuryMessageStore); chatManager.addIncomingListener(mercuryMessageStore);
chatManager.addOutgoingListener(mercuryMessageStore);
})); }));
cryptoManager.initialize(connection); cryptoManager.initialize(connection);
} }

View File

@ -1,42 +0,0 @@
package org.mercury_im.messenger.core.connection
import org.mercury_im.messenger.entity.message.Message as MercuryMessage;
import org.jivesoftware.smack.packet.Message as SmackMessage;
import java.util.UUID
import io.reactivex.Completable
import io.reactivex.Single
import org.mercury_im.messenger.core.util.AppendCompletableToSingle
import org.mercury_im.messenger.entity.chat.Chat
class MessageCenter(private val composer: Composer,
private val persister: Persister,
private val encryptor: Encryptor,
private val sender: Sender) {
fun sendSingleTextMessage(chat: Chat, body: String): Single<UUID> {
val messageEntity = composer.createChatMessage(chat, body)
val messageId = persister.persistPendingMessage(messageEntity)
val smackMessage = encryptor.encrypt(messageEntity)
val sendCompletable = sender.send(smackMessage);
return messageId.compose(AppendCompletableToSingle(sendCompletable));
}
interface Composer {
fun createChatMessage(chat: Chat, body: String): MercuryMessage
}
interface Persister {
fun persistPendingMessage(message: MercuryMessage): Single<UUID>
}
interface Encryptor {
fun encrypt(message: MercuryMessage): SmackMessage
}
interface Sender {
fun send(message: SmackMessage): Completable
}
}

View File

@ -1,22 +0,0 @@
package org.mercury_im.messenger.core.connection;
import org.mercury_im.messenger.core.data.repository.MessageRepository;
import org.mercury_im.messenger.entity.message.Message;
import java.util.UUID;
import io.reactivex.Single;
public class MessagePersister implements MessageCenter.Persister {
private final MessageRepository messageRepository;
public MessagePersister(MessageRepository messageRepository) {
this.messageRepository = messageRepository;
}
@Override
public Single<UUID> persistPendingMessage(Message message) {
return null;
}
}

View File

@ -1,6 +1,7 @@
package org.mercury_im.messenger.core.connection; package org.mercury_im.messenger.core.connection;
import org.jivesoftware.smack.ReconnectionManager; import org.jivesoftware.smack.ReconnectionManager;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.roster.Roster; import org.jivesoftware.smack.roster.Roster;
import org.jivesoftware.smackx.carbons.CarbonManager; import org.jivesoftware.smackx.carbons.CarbonManager;
import org.jivesoftware.smackx.iqversion.VersionManager; import org.jivesoftware.smackx.iqversion.VersionManager;
@ -10,7 +11,7 @@ import org.jivesoftware.smackx.sid.StableUniqueStanzaIdManager;
public class SmackConfig { public class SmackConfig {
static void staticConfiguration() { static void staticConfiguration() {
//SmackConfiguration.DEBUG = true; SmackConfiguration.DEBUG = true;
ReconnectionManager.setEnabledPerDefault(true); ReconnectionManager.setEnabledPerDefault(true);
ReconnectionManager.setDefaultReconnectionPolicy(ReconnectionManager.ReconnectionPolicy.RANDOM_INCREASING_DELAY); ReconnectionManager.setDefaultReconnectionPolicy(ReconnectionManager.ReconnectionPolicy.RANDOM_INCREASING_DELAY);

View File

@ -1,19 +1,31 @@
package org.mercury_im.messenger.core.connection; package org.mercury_im.messenger.core.connection;
import org.jivesoftware.smack.AbstractXMPPConnection; import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.packet.id.StanzaIdSource;
import org.jivesoftware.smack.packet.id.StanzaIdSourceFactory;
import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jxmpp.stringprep.XmppStringprepException; import org.jxmpp.stringprep.XmppStringprepException;
import org.mercury_im.messenger.entity.Account; import org.mercury_im.messenger.entity.Account;
import javax.inject.Inject;
public class XmppTcpConnectionFactory implements XmppConnectionFactory { public class XmppTcpConnectionFactory implements XmppConnectionFactory {
private static final int CONNECTION_TIMEOUT = 30 * 1000; private static final int CONNECTION_TIMEOUT = 30 * 1000;
private final StanzaIdSourceFactory stanzaIdSourceFactory;
@Inject
public XmppTcpConnectionFactory(StanzaIdSourceFactory stanzaIdSourceFactory) {
this.stanzaIdSourceFactory = stanzaIdSourceFactory;
}
public AbstractXMPPConnection createConnection(Account account) { public AbstractXMPPConnection createConnection(Account account) {
try { try {
XMPPTCPConnectionConfiguration.Builder configBuilder = XMPPTCPConnectionConfiguration.Builder configBuilder =
XMPPTCPConnectionConfiguration.builder() XMPPTCPConnectionConfiguration.builder()
.setStanzaIdSourceFactory(stanzaIdSourceFactory)
.setConnectTimeout(CONNECTION_TIMEOUT) .setConnectTimeout(CONNECTION_TIMEOUT)
.setXmppAddressAndPassword(account.getAddress(), account.getPassword()); .setXmppAddressAndPassword(account.getAddress(), account.getPassword());
if (account.getHost() != null) { if (account.getHost() != null) {

View File

@ -0,0 +1,24 @@
package org.mercury_im.messenger.core.connection.message;
import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
import org.mercury_im.messenger.core.data.repository.MessageRepository;
import org.mercury_im.messenger.entity.chat.Chat;
public abstract class AbstractMessageConsignor implements MessageConsignor {
protected final MessageComposer messageComposer;
protected final MercuryConnectionManager connectionManager;
protected final MessageRepository messageRepository;
protected final Chat chat;
public AbstractMessageConsignor(MessageComposer composer,
MercuryConnectionManager connectionManager,
MessageRepository messageRepository,
Chat chat) {
this.messageComposer = composer;
this.connectionManager = connectionManager;
this.messageRepository = messageRepository;
this.chat = chat;
}
}

View File

@ -1,27 +1,33 @@
package org.mercury_im.messenger.core.connection; package org.mercury_im.messenger.core.connection.message;
import org.jivesoftware.smack.packet.id.StandardStanzaIdSource;
import org.mercury_im.messenger.entity.chat.Chat; import org.mercury_im.messenger.entity.chat.Chat;
import org.mercury_im.messenger.entity.message.Message; import org.mercury_im.messenger.entity.message.Message;
import org.mercury_im.messenger.entity.message.MessageDeliveryState; import org.mercury_im.messenger.entity.message.MessageDeliveryState;
import org.mercury_im.messenger.entity.message.MessageDirection; import org.mercury_im.messenger.entity.message.MessageDirection;
import java.util.Date;
import java.util.UUID; import java.util.UUID;
public class MercuryMessageComposer implements MessageCenter.Composer { public class MessageComposer {
@Override
public Message createChatMessage(Chat chat, String body) { public Message createChatMessage(Chat chat, String body) {
UUID messageId = UUID.randomUUID();
Message message = new Message(); Message message = new Message();
message.setId(messageId);
message.setChatId(chat.getId());
message.setLegacyStanzaId(new StandardStanzaIdSource().getNewStanzaId());
message.setOriginId(messageId.toString());
message.setBody(body); message.setBody(body);
message.setSender(chat.getAccount().getJid()); message.setSender(chat.getAccount().getJid());
message.setRecipient(chat.getAddress()); message.setRecipient(chat.getJid());
message.setDeliveryState(MessageDeliveryState.pending_delivery); message.setDeliveryState(MessageDeliveryState.pending_delivery);
message.setDirection(MessageDirection.outgoing); message.setDirection(MessageDirection.outgoing);
message.setRead(false); message.setRead(false);
message.setTimestamp(new Date());
UUID messageId = UUID.randomUUID();
message.setId(messageId);
message.setOriginId(messageId.toString());
return message; return message;
} }
} }

View File

@ -0,0 +1,4 @@
package org.mercury_im.messenger.core.connection.message;
public interface MessageConsignee {
}

View File

@ -0,0 +1,12 @@
package org.mercury_im.messenger.core.connection.message;
import org.mercury_im.messenger.entity.chat.Chat;
import java.util.UUID;
import io.reactivex.Single;
public interface MessageConsignor {
Single<UUID> sendTextMessage(Chat chat, String body);
}

View File

@ -0,0 +1,25 @@
package org.mercury_im.messenger.core.connection.message;
import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
import org.mercury_im.messenger.core.data.repository.MessageRepository;
import org.mercury_im.messenger.entity.chat.Chat;
import java.util.UUID;
import io.reactivex.Single;
public class OxMessageConsignor extends AbstractMessageConsignor {
public OxMessageConsignor(MercuryConnectionManager connectionManager, MessageRepository messageRepository, Chat chat) {
this(new MessageComposer(), connectionManager, messageRepository, chat);
}
public OxMessageConsignor(MessageComposer composer, MercuryConnectionManager connectionManager, MessageRepository messageRepository, Chat chat) {
super(composer, connectionManager, messageRepository, chat);
}
@Override
public Single<UUID> sendTextMessage(Chat chat, String body) {
return null;
}
}

View File

@ -0,0 +1,91 @@
package org.mercury_im.messenger.core.connection.message;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.packet.MessageBuilder;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jivesoftware.smackx.muc.MultiUserChatManager;
import org.jivesoftware.smackx.sid.element.OriginIdElement;
import org.jxmpp.jid.parts.Resourcepart;
import org.mercury_im.messenger.core.connection.MercuryConnection;
import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
import org.mercury_im.messenger.core.data.repository.MessageRepository;
import org.mercury_im.messenger.core.util.AppendCompletableToSingle;
import org.mercury_im.messenger.entity.chat.Chat;
import org.mercury_im.messenger.entity.chat.DirectChat;
import org.mercury_im.messenger.entity.chat.GroupChat;
import org.mercury_im.messenger.entity.message.Message;
import org.mercury_im.messenger.entity.message.MessageDeliveryState;
import java.util.UUID;
import io.reactivex.Completable;
import io.reactivex.Single;
public class PlaintextMessageConsignor extends AbstractMessageConsignor {
public PlaintextMessageConsignor(MercuryConnectionManager connectionManager, MessageRepository messageRepository, Chat chat) {
this(new MessageComposer(), connectionManager, messageRepository, chat);
}
public PlaintextMessageConsignor(MessageComposer composer,
MercuryConnectionManager connectionManager,
MessageRepository messageRepository,
Chat chat) {
super(composer, connectionManager, messageRepository, chat);
}
@Override
public Single<UUID> sendTextMessage(Chat chat, String body) {
Message message = messageComposer.createChatMessage(chat, body);
MessageBuilder messageBuilder = commonMessageBuilder(message);
Completable deliverMessage;
if (chat instanceof DirectChat) {
deliverMessage = sendDirectChatMessage((DirectChat) chat, messageBuilder);
} else if (chat instanceof GroupChat) {
deliverMessage = sendGroupChatMessage((GroupChat) chat, messageBuilder);
} else {
throw new AssertionError("Unknown chat type.");
}
Single<UUID> deliverAndStore = messageRepository.insertMessage(message)
.map(Message::getId)
.compose(new AppendCompletableToSingle<>(deliverMessage))
.flatMap(messageId -> messageRepository.updateDeliveryState(messageId, MessageDeliveryState.delivered_to_server)
.toSingle(() -> messageId));
return deliverAndStore;
}
private MessageBuilder commonMessageBuilder(Message message) {
return MessageBuilder.buildMessage(message.getLegacyStanzaId())
.ofType(org.jivesoftware.smack.packet.Message.Type.chat)
.addExtension(new OriginIdElement(message.getOriginId()))
.to(chat.getJid())
.from(chat.getAccount().getJid())
.setBody(message.getBody());
}
private Completable sendDirectChatMessage(DirectChat chat, MessageBuilder messageBuilder) {
return Completable.fromAction(() -> {
MercuryConnection connection = connectionManager.getConnection(chat.getAccount());
ChatManager chatManager = ChatManager.getInstanceFor(connection.getConnection());
org.jivesoftware.smack.chat2.Chat smackChat = chatManager.chatWith(chat.getJid().asEntityBareJid());
smackChat.send(messageBuilder.build());
});
}
private Completable sendGroupChatMessage(GroupChat chat, MessageBuilder messageBuilder) {
return Completable.fromAction(() -> {
MercuryConnection connection = connectionManager.getConnection(chat.getAccount());
MultiUserChatManager multiUserChatManager = MultiUserChatManager.getInstanceFor(connection.getConnection());
MultiUserChat muc = multiUserChatManager.getMultiUserChat(chat.getJid().asEntityBareJid());
if (!muc.isJoined()) {
muc.join(Resourcepart.from(chat.getNickname()));
}
muc.sendMessage(messageBuilder);
});
}
}

View File

@ -1,10 +1,14 @@
package org.mercury_im.messenger.core.data.repository; package org.mercury_im.messenger.core.data.repository;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.entity.chat.DirectChat; import org.mercury_im.messenger.entity.chat.DirectChat;
import org.mercury_im.messenger.entity.chat.GroupChat; import org.mercury_im.messenger.entity.chat.GroupChat;
import org.mercury_im.messenger.entity.message.ChatMarkerState;
import org.mercury_im.messenger.entity.message.Message; import org.mercury_im.messenger.entity.message.Message;
import org.mercury_im.messenger.entity.message.MessageDeliveryState;
import java.util.List; import java.util.List;
import java.util.UUID;
import io.reactivex.Completable; import io.reactivex.Completable;
import io.reactivex.Observable; import io.reactivex.Observable;
@ -29,4 +33,8 @@ public interface MessageRepository {
Single<Message> updateMessage(Message message); Single<Message> updateMessage(Message message);
Completable deleteMessage(Message message); Completable deleteMessage(Message message);
Completable markMessage(String stanzaId, EntityBareJid xmppAddressOfChatPartner, ChatMarkerState received);
Completable updateDeliveryState(UUID messageId, MessageDeliveryState deliveryState);
} }

View File

@ -0,0 +1,19 @@
package org.mercury_im.messenger.core.di.module;
import org.jivesoftware.smack.packet.id.StandardStanzaIdSource;
import org.jivesoftware.smack.packet.id.StanzaIdSourceFactory;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class StanzaIdSourceFactoryModule {
@Provides
@Singleton
static StanzaIdSourceFactory provideStanzaIdSourceFactory() {
return new StandardStanzaIdSource.Factory();
}
}

View File

@ -1,5 +1,6 @@
package org.mercury_im.messenger.core.di.module; package org.mercury_im.messenger.core.di.module;
import org.jivesoftware.smack.packet.id.StanzaIdSourceFactory;
import org.mercury_im.messenger.core.connection.XmppConnectionFactory; import org.mercury_im.messenger.core.connection.XmppConnectionFactory;
import org.mercury_im.messenger.core.connection.XmppTcpConnectionFactory; import org.mercury_im.messenger.core.connection.XmppTcpConnectionFactory;
@ -13,7 +14,7 @@ public class XmppTcpConnectionFactoryModule {
@Provides @Provides
@Singleton @Singleton
static XmppConnectionFactory provideConnectionFactory() { static XmppConnectionFactory provideConnectionFactory(StanzaIdSourceFactory stanzaIdSourceFactory) {
return new XmppTcpConnectionFactory(); return new XmppTcpConnectionFactory(stanzaIdSourceFactory);
} }
} }

View File

@ -29,7 +29,7 @@ import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
@Singleton @Singleton
public class MercuryMessageStore implements IncomingChatMessageListener, OutgoingChatMessageListener, OxMessageListener { public class MercuryMessageStore implements IncomingChatMessageListener, OxMessageListener {
private static final Logger LOGGER = Logger.getLogger(MercuryMessageStore.class.getName()); private static final Logger LOGGER = Logger.getLogger(MercuryMessageStore.class.getName());
@ -66,8 +66,8 @@ public class MercuryMessageStore implements IncomingChatMessageListener, Outgoin
message.setDirection(MessageDirection.incoming); message.setDirection(MessageDirection.incoming);
DelayInformation delayInformation = DelayInformation.from(smackMessage); DelayInformation delayInformation = DelayInformation.from(smackMessage);
message.setTimestamp(delayInformation != null ? delayInformation.getStamp() : new Date()); message.setTimestamp(delayInformation != null ? delayInformation.getStamp() : new Date());
message.setSender(from.asEntityBareJidString()); message.setSender(from);
message.setRecipient(smackMessage.getTo().asBareJid().toString()); message.setRecipient(smackMessage.getTo().asEntityJidOrThrow());
message.setXml(smackMessage.toXML().toString()); message.setXml(smackMessage.toXML().toString());
if (smackMessage.getBody() != null) { if (smackMessage.getBody() != null) {
message.setBody(smackMessage.getBody()); message.setBody(smackMessage.getBody());
@ -75,25 +75,6 @@ public class MercuryMessageStore implements IncomingChatMessageListener, Outgoin
disposable.add(writeMessageToStore(from, message)); disposable.add(writeMessageToStore(from, message));
} }
@Override
public void newOutgoingMessage(EntityBareJid to,
MessageBuilder smackMessage,
org.jivesoftware.smack.chat2.Chat smackChat) {
if (smackMessage.hasExtension(ExplicitMessageEncryptionElement.QNAME)) {
return;
}
Message message = new Message();
message.setDirection(MessageDirection.outgoing);
message.setTimestamp(new Date());
message.setSender(account.getAddress());
message.setRecipient(to.asBareJid().toString());
message.setXml(smackMessage.build().toXML().toString());
if (smackMessage.getBody() != null) {
message.setBody(smackMessage.getBody());
}
disposable.add(writeMessageToStore(to, message));
}
private Disposable writeMessageToStore(EntityBareJid peer, Message message) { private Disposable writeMessageToStore(EntityBareJid peer, Message message) {
return peerRepository.getOrCreatePeer(account, peer) return peerRepository.getOrCreatePeer(account, peer)
.flatMap(directChatRepository::getOrCreateChatWithPeer) .flatMap(directChatRepository::getOrCreateChatWithPeer)
@ -115,8 +96,8 @@ public class MercuryMessageStore implements IncomingChatMessageListener, Outgoin
message.setDirection(MessageDirection.incoming); message.setDirection(MessageDirection.incoming);
DelayInformation delayInformation = DelayInformation.from(smackMessage); DelayInformation delayInformation = DelayInformation.from(smackMessage);
message.setTimestamp(delayInformation != null ? delayInformation.getStamp() : new Date()); message.setTimestamp(delayInformation != null ? delayInformation.getStamp() : new Date());
message.setSender(contact.getJid().toString()); message.setSender(contact.getJid().asEntityJidOrThrow());
message.setRecipient(smackMessage.getTo().asBareJid().toString()); message.setRecipient(smackMessage.getTo().asEntityJidOrThrow());
message.setXml(smackMessage.toXML().toString()); message.setXml(smackMessage.toXML().toString());
org.jivesoftware.smack.packet.Message.Body body = decryptedPayload.getExtension(org.jivesoftware.smack.packet.Message.Body.ELEMENT, org.jivesoftware.smack.packet.Message.Body body = decryptedPayload.getExtension(org.jivesoftware.smack.packet.Message.Body.ELEMENT,
org.jivesoftware.smack.packet.Message.Body.NAMESPACE); org.jivesoftware.smack.packet.Message.Body.NAMESPACE);

View File

@ -117,11 +117,11 @@ public class LoginViewModel implements MercuryViewModel {
Account account = createAccountEntity(); Account account = createAccountEntity();
MercuryConnection connection = connectionManager.createConnection(account); //MercuryConnection connection = connectionManager.createConnection(account);
addDisposable(accountRepository.upsertAccount(account).ignoreElement() addDisposable(accountRepository.upsertAccount(account).ignoreElement()
.andThen(connection.connect()) //.andThen(connection.connect())
.andThen(connection.login()) //.andThen(connection.login())
.andThen(connectionManager.registerConnection(connection)) //.andThen(connectionManager.registerConnection(connection))
.subscribeOn(schedulers.getNewThread()) .subscribeOn(schedulers.getNewThread())
.observeOn(schedulers.getUiScheduler()) .observeOn(schedulers.getUiScheduler())
.subscribe( .subscribe(

View File

@ -19,6 +19,7 @@ import java.util.logging.Logger;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.Single; import io.reactivex.Single;
import io.reactivex.functions.Function;
import io.reactivex.subjects.BehaviorSubject; import io.reactivex.subjects.BehaviorSubject;
import lombok.Getter; import lombok.Getter;
@ -35,8 +36,6 @@ public class ChatViewModel implements MercuryViewModel {
@Getter @Getter
private BehaviorSubject<Optional<Peer>> peer = BehaviorSubject.create(); private BehaviorSubject<Optional<Peer>> peer = BehaviorSubject.create();
private Single<DirectChat> directChat;
@Getter @Getter
private BehaviorSubject<DirectChat> chat = BehaviorSubject.create(); private BehaviorSubject<DirectChat> chat = BehaviorSubject.create();
@ -62,13 +61,20 @@ public class ChatViewModel implements MercuryViewModel {
} }
public void init(UUID accountId, EntityBareJid contactJid) { public void init(UUID accountId, EntityBareJid contactJid) {
Single<Peer> peerSingle = contactRepository.getOrCreatePeer(accountId, contactJid); contactRepository.getOrCreatePeer(accountId, contactJid)
peerSingle.flatMapObservable(contactRepository::observePeer).subscribe(peer); .flatMapObservable(contactRepository::observePeer)
directChat = peerSingle.flatMap(directChatRepository::getOrCreateChatWithPeer); .subscribe(peer);
directChat.toObservable().compose(schedulers.executeUiSafeObservable()).subscribe(chat); peer.compose(schedulers.executeUiSafeObservable())
.subscribe();
Observable<List<Message>> allMessagesObservable = directChat.flatMapObservable(messageRepository::observeMessages); peer.filter(Optional::isPresent).map(Optional::getItem)
.flatMap(p -> directChatRepository.getOrCreateChatWithPeer(p).toObservable())
.subscribe(chat);
chat.compose(schedulers.executeUiSafeObservable()).subscribe();
Observable<List<Message>> allMessagesObservable = chat.flatMap(messageRepository::observeMessages);
messageQueryObservable.onNext(allMessagesObservable); messageQueryObservable.onNext(allMessagesObservable);
messages = Observable.switchOnNext(messageQueryObservable); messages = Observable.switchOnNext(messageQueryObservable);
contactDisplayName = peer.filter(Optional::isPresent).map(Optional::getItem) contactDisplayName = peer.filter(Optional::isPresent).map(Optional::getItem)
@ -77,9 +83,9 @@ public class ChatViewModel implements MercuryViewModel {
public void onQueryTextChanged(String query) { public void onQueryTextChanged(String query) {
if (query.trim().isEmpty()) { if (query.trim().isEmpty()) {
messageQueryObservable.onNext(directChat.flatMapObservable(messageRepository::observeMessages)); messageQueryObservable.onNext(chat.flatMap(messageRepository::observeMessages));
} else { } else {
messageQueryObservable.onNext(directChat.flatMapObservable(c -> messageRepository.findMessagesWithBody(c, query))); messageQueryObservable.onNext(chat.flatMap(c -> messageRepository.findMessagesWithBody(c, query)));
} }
} }
@ -95,9 +101,10 @@ public class ChatViewModel implements MercuryViewModel {
} }
public void sendMessage(String body) { public void sendMessage(String body) {
addDisposable(messenger.sendEncryptedMessage(peer.getValue().getItem(), body) addDisposable(messenger.getMessageConsignor(chat.getValue())
.compose(schedulers.executeUiSafeCompletable()) .sendTextMessage(chat.getValue(), body)
.subscribe(() -> LOGGER.log(Level.INFO, "Successfully sent encrypted message."), .compose(schedulers.executeUiSafeSingle())
e -> LOGGER.log(Level.SEVERE, "Error sending encrypted message.", e))); .subscribe(messageId -> LOGGER.log(Level.INFO, "Successfully sent message."),
e -> LOGGER.log(Level.WARNING, "Error sending message.", e)));
} }
} }

View File

@ -0,0 +1,111 @@
package org.mercury_im.messenger.learning_tests.dagger;
import org.junit.Test;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import static junit.framework.TestCase.assertEquals;
public class DaggerTest {
@dagger.Component(modules = {SingletonModule.class})
@Singleton
public interface Component {
void inject(Consumer consumer);
}
@Module
public class NewInstanceModule {
private int DEPENDENCY_INDEX = 0;
private final String MODULE_NAME;
public NewInstanceModule(String moduleName) {
this.MODULE_NAME = moduleName;
}
@Provides
Dependency provideDependency() {
return new Dependency(DEPENDENCY_INDEX++, MODULE_NAME);
}
}
@Module
public class SingletonModule {
private int DEPENDENCY_INDEX = 0;
private final String MODULE_NAME;
public SingletonModule(String moduleName) {
this.MODULE_NAME = moduleName;
}
@Provides
@Singleton
Dependency provideDependency() {
return new Dependency(DEPENDENCY_INDEX++, MODULE_NAME);
}
}
public static class Dependency {
final int index;
final String moduleName;
@Inject
public Dependency(int index, String moduleName) {
this.index = index;
this.moduleName = moduleName;
}
public int getIndex() {
return index;
}
public String getModuleName() {
return moduleName;
}
}
public static class Consumer {
@Inject
Dependency dependency;
public Consumer() {
}
public Dependency getDependency() {
return dependency;
}
}
@Test
public void test() {
Component component0 = DaggerDaggerTest_Component.builder().singletonModule(new SingletonModule("First")).build();
Consumer consumer0 = new Consumer();
Consumer consumer1 = new Consumer();
component0.inject(consumer0);
component0.inject(consumer1);
Component component1 = DaggerDaggerTest_Component.builder().singletonModule(new SingletonModule("Second")).build();
Consumer consumer2 = new Consumer();
Consumer consumer3 = new Consumer();
component1.inject(consumer2);
component1.inject(consumer3);
assertEquals(0, consumer0.getDependency().getIndex());
assertEquals(1, consumer1.getDependency().getIndex());
assertEquals(0, consumer2.getDependency().getIndex());
assertEquals(1, consumer3.getDependency().getIndex());
}
}

View File

@ -0,0 +1,26 @@
package org.mercury_im.messenger.learning_tests.rx;
import org.junit.Test;
import io.reactivex.Observable;
import io.reactivex.subjects.BehaviorSubject;
import io.reactivex.subjects.Subject;
import static junit.framework.TestCase.assertNotNull;
public class BehaviourSubjectSubscriptionTest {
@Test
public void test() throws InterruptedException {
Observable<String> observable = Observable.just("One", "Two", "Three");
BehaviorSubject<String> behaviorSubject = BehaviorSubject.create();
observable.subscribe(behaviorSubject);
behaviorSubject.subscribe(System.out::println);
Thread.sleep(100);
String s = behaviorSubject.getValue();
assertNotNull(s);
}
}

View File

@ -1,5 +1,7 @@
package org.mercury_im.messenger.entity.chat; package org.mercury_im.messenger.entity.chat;
import org.jxmpp.jid.EntityJid;
import org.jxmpp.jid.impl.JidCreate;
import org.mercury_im.messenger.entity.Account; import org.mercury_im.messenger.entity.Account;
import java.util.UUID; import java.util.UUID;
@ -15,11 +17,15 @@ import lombok.Data;
public abstract class Chat { public abstract class Chat {
UUID id; UUID id;
Account account; Account account;
ChatPreferences chatPreferences; ChatPreferences chatPreferences = new ChatPreferences();
public Chat() { public Chat() {
this.id = UUID.randomUUID(); this.id = UUID.randomUUID();
} }
public abstract String getAddress(); public abstract String getAddress();
public EntityJid getJid() {
return JidCreate.entityFromOrThrowUnchecked(getAddress());
}
} }

View File

@ -1,5 +1,7 @@
package org.mercury_im.messenger.entity.chat; package org.mercury_im.messenger.entity.chat;
import org.mercury_im.messenger.entity.Encryption;
import lombok.Data; import lombok.Data;
/** /**
@ -12,6 +14,7 @@ public class ChatPreferences {
boolean sendTypingNotificationsEnabled; boolean sendTypingNotificationsEnabled;
boolean readNotificationsSupported; boolean readNotificationsSupported;
boolean sendReadNotificationsEnabled; boolean sendReadNotificationsEnabled;
Encryption encryption = Encryption.plain; // TODO: Fix
@Data @Data
public static class NotificationPreferences { public static class NotificationPreferences {

View File

@ -16,6 +16,7 @@ public class GroupChat extends Chat {
Set<Peer> participants; Set<Peer> participants;
String roomAddress; String roomAddress;
String roomName; String roomName;
String nickname = "NICK"; // TODO: Fix
@Override @Override
public String getAddress() { public String getAddress() {

View File

@ -0,0 +1,8 @@
package org.mercury_im.messenger.entity.message;
public enum ChatMarkerState {
markable,
received,
displayed,
acknowledged
}

View File

@ -1,10 +1,9 @@
package org.mercury_im.messenger.entity.message; package org.mercury_im.messenger.entity.message;
import org.jxmpp.jid.EntityFullJid; import org.jxmpp.jid.EntityJid;
import org.mercury_im.messenger.entity.Encryption; import org.mercury_im.messenger.entity.Encryption;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import lombok.Data; import lombok.Data;
@ -13,8 +12,8 @@ import lombok.Data;
public class Message { public class Message {
UUID id; UUID id;
UUID chatId; UUID chatId;
EntityFullJid sender; EntityJid sender;
EntityFullJid recipient; EntityJid recipient;
String body; String body;
@ -34,6 +33,7 @@ public class Message {
Encryption encryption; Encryption encryption;
boolean received; boolean received;
boolean read; boolean read;
boolean pending;
public boolean isIncoming() { public boolean isIncoming() {
return getDirection() == MessageDirection.incoming; return getDirection() == MessageDirection.incoming;

View File

@ -1,6 +1,6 @@
#Sun Sep 01 01:05:38 CEST 2019 #Mon Jul 27 15:17:47 CEST 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip

@ -1 +1 @@
Subproject commit 1267729430f2c53f966b98febf16ef7f262b94d7 Subproject commit 30030941307262173ac7347e11235a8cadf68787

View File

@ -86,7 +86,7 @@ ext {
rxAndroidVersion = "2.1.1" rxAndroidVersion = "2.1.1"
// Dagger 2 // Dagger 2
daggerVersion = '2.25.4' daggerVersion = '2.28.3'
// Lombok // Lombok
lombokVersion = '1.18.12' lombokVersion = '1.18.12'