Some code to work with conversations

This commit is contained in:
2017-03-13 22:49:40 +01:00
parent 10a45cc79c
commit d9090eb70c
16 changed files with 573 additions and 39 deletions

View File

@ -87,7 +87,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener, Internal
BitmessageAddress identity = ctx.getAddressRepository().findIdentity(getPubkey.getRipeTag());
if (identity != null && identity.getPrivateKey() != null && !identity.isChan()) {
LOG.info("Got pubkey request for identity " + identity);
// FIXME: only send pubkey if it wasn't sent in the last 28 days
// FIXME: only send pubkey if it wasn't sent in the last TTL.pubkey() days
ctx.sendPubkey(identity, object.getStream());
}
}

View File

@ -18,22 +18,20 @@ package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.payload.Msg;
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
import ch.dissem.bitmessage.entity.valueobject.extended.Attachment;
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.entity.valueobject.extended.Attachment;
import ch.dissem.bitmessage.entity.valueobject.extended.Message;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.bitmessage.factory.ExtendedEncodingFactory;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.TTL;
import ch.dissem.bitmessage.utils.UnixTime;
import ch.dissem.bitmessage.utils.*;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.Collections;
import static ch.dissem.bitmessage.entity.Plaintext.Encoding.EXTENDED;
import static ch.dissem.bitmessage.entity.Plaintext.Encoding.SIMPLE;
@ -50,6 +48,7 @@ public class Plaintext implements Streamable {
private final long encoding;
private final byte[] message;
private final byte[] ackData;
private final UUID conversationId;
private ExtendedEncoding extendedData;
private ObjectMessage ackMessage;
private Object id;
@ -90,6 +89,7 @@ public class Plaintext implements Streamable {
ttl = builder.ttl;
retries = builder.retries;
nextTry = builder.nextTry;
conversationId = builder.conversation;
}
public static Plaintext read(Type type, InputStream in) throws IOException {
@ -390,7 +390,7 @@ public class Plaintext implements Streamable {
}
public List<InventoryVector> getParents() {
if (Message.TYPE.equals(getExtendedData().getType())) {
if (getExtendedData() != null && Message.TYPE.equals(getExtendedData().getType())) {
return ((Message) extendedData.getContent()).getParents();
} else {
return Collections.emptyList();
@ -405,6 +405,10 @@ public class Plaintext implements Streamable {
}
}
public UUID getConversationId() {
return conversationId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@ -470,6 +474,16 @@ public class Plaintext implements Streamable {
return initialHash;
}
@Override
public String toString() {
String subject = getSubject();
if (subject == null || subject.length() == 0) {
return Strings.hex(initialHash).toString();
} else {
return subject;
}
}
public enum Encoding {
IGNORE(0), TRIVIAL(1), SIMPLE(2), EXTENDED(3);
@ -527,12 +541,13 @@ public class Plaintext implements Streamable {
private byte[] ackMessage;
private byte[] signature;
private long sent;
private long received;
private Long received;
private Status status;
private Set<Label> labels = new HashSet<>();
private long ttl;
private int retries;
private Long nextTry;
private UUID conversation;
public Builder(Type type) {
this.type = type;
@ -685,6 +700,11 @@ public class Plaintext implements Streamable {
return this;
}
public Builder conversation(UUID id) {
this.conversation = id;
return this;
}
public Plaintext build() {
if (from == null) {
from = new BitmessageAddress(Factory.createPubkey(
@ -706,6 +726,9 @@ public class Plaintext implements Streamable {
if (ttl <= 0) {
ttl = TTL.msg();
}
if (conversation == null) {
conversation = UUID.randomUUID();
}
return new Plaintext(this);
}
}

View File

@ -19,13 +19,16 @@ package ch.dissem.bitmessage.ports;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.bitmessage.utils.Strings;
import ch.dissem.bitmessage.utils.UnixTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import static ch.dissem.bitmessage.utils.SqlStrings.join;
@ -71,6 +74,11 @@ public abstract class AbstractMessageRepository implements MessageRepository, In
}
}
@Override
public Plaintext getMessage(InventoryVector iv) {
return single(find("iv=X'" + Strings.hex(iv.getHash()) + "'"));
}
@Override
public Plaintext getMessage(byte[] initialHash) {
return single(find("initial_hash=X'" + Strings.hex(initialHash) + "'"));
@ -111,6 +119,20 @@ public abstract class AbstractMessageRepository implements MessageRepository, In
" AND next_try < " + UnixTime.now());
}
@Override
public List<Plaintext> findResponses(Plaintext parent) {
if (parent.getInventoryVector() == null) {
return Collections.emptyList();
}
return find("iv IN (SELECT child FROM Message_Parent"
+ " WHERE parent=X'" + Strings.hex(parent.getInventoryVector().getHash()) + "')");
}
@Override
public List<Plaintext> getConversation(UUID conversationId) {
return find("conversation=X'" + conversationId.toString().replace("-", "") + "'");
}
@Override
public List<Label> getLabels() {
return findLabels("1=1");

View File

@ -19,9 +19,12 @@ package ch.dissem.bitmessage.ports;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.Plaintext.Status;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
public interface MessageRepository {
List<Label> getLabels();
@ -32,10 +35,18 @@ public interface MessageRepository {
Plaintext getMessage(Object id);
Plaintext getMessage(InventoryVector iv);
Plaintext getMessage(byte[] initialHash);
Plaintext getMessageForAck(byte[] ackData);
/**
* @param label to search for
* @return a distinct list of all conversations that have at least one message with the given label.
*/
List<UUID> findConversations(Label label);
List<Plaintext> findMessages(Label label);
List<Plaintext> findMessages(Status status);
@ -44,9 +55,21 @@ public interface MessageRepository {
List<Plaintext> findMessages(BitmessageAddress sender);
List<Plaintext> findResponses(Plaintext parent);
List<Plaintext> findMessagesToResend();
void save(Plaintext message);
void remove(Plaintext message);
/**
* Returns all messages with this conversation ID. The returned messages aren't sorted in any way,
* so you may prefer to use {@link ch.dissem.bitmessage.utils.ConversationService#getConversation(UUID)}
* instead.
*
* @param conversationId ID of the requested conversation
* @return all messages with the given conversation ID
*/
Collection<Plaintext> getConversation(UUID conversationId);
}

View File

@ -24,6 +24,12 @@ import java.util.List;
* Stores and provides known peers.
*/
public interface NodeRegistry {
/**
* Removes all known nodes from registry. This should work around connection issues
* when there are many invalid nodes in the registry.
*/
void clear();
List<NetworkAddress> getKnownAddresses(int limit, long... streams);
void offerAddresses(List<NetworkAddress> addresses);

View File

@ -0,0 +1,112 @@
/*
* Copyright 2017 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.bitmessage.utils;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.ports.MessageRepository;
import java.util.*;
import java.util.Collections;
/**
* Helper service to work with conversations
*/
public class ConversationService {
private final MessageRepository messageRepository;
public ConversationService(MessageRepository messageRepository) {
this.messageRepository = messageRepository;
}
public List<Plaintext> getConversation(Plaintext message) {
return getConversation(message.getConversationId());
}
private LinkedList<Plaintext> sorted(Collection<Plaintext> collection) {
LinkedList<Plaintext> result = new LinkedList<>(collection);
Collections.sort(result, new Comparator<Plaintext>() {
@Override
public int compare(Plaintext o1, Plaintext o2) {
//noinspection NumberEquality - if both are null (if both are the same, it's a bonus)
if (o1.getReceived() == o2.getReceived()) {
return 0;
}
if (o1.getReceived() == null) {
return -1;
}
if (o2.getReceived() == null) {
return 1;
}
return -o1.getReceived().compareTo(o2.getReceived());
}
});
return result;
}
public List<Plaintext> getConversation(UUID conversationId) {
LinkedList<Plaintext> messages = sorted(messageRepository.getConversation(conversationId));
Map<InventoryVector, Plaintext> map = new HashMap<>(messages.size());
for (Plaintext message : messages) {
if (message.getInventoryVector() != null) {
map.put(message.getInventoryVector(), message);
}
}
LinkedList<Plaintext> result = new LinkedList<>();
while (!messages.isEmpty()) {
Plaintext last = messages.poll();
int pos = lastParentPosition(last, result);
result.add(pos, last);
addAncestors(last, result, messages, map);
}
return result;
}
private int lastParentPosition(Plaintext child, LinkedList<Plaintext> messages) {
Iterator<Plaintext> plaintextIterator = messages.descendingIterator();
int i = 0;
while (plaintextIterator.hasNext()) {
Plaintext next = plaintextIterator.next();
if (isParent(next, child)) {
break;
}
i++;
}
return messages.size() - i;
}
private boolean isParent(Plaintext item, Plaintext child) {
for (InventoryVector parentId : child.getParents()) {
if (parentId.equals(item.getInventoryVector())) {
return true;
}
}
return false;
}
private void addAncestors(Plaintext message, LinkedList<Plaintext> result, LinkedList<Plaintext> messages, Map<InventoryVector, Plaintext> map) {
for (InventoryVector parentKey : message.getParents()) {
Plaintext parent = map.remove(parentKey);
if (parent != null) {
messages.remove(parent);
result.addFirst(parent);
addAncestors(parent, result, messages, map);
}
}
}
}

View File

@ -138,7 +138,7 @@ public class Decode {
public static String varString(InputStream in, AccessCounter counter) throws IOException {
int length = (int) varInt(in, counter);
// FIXME: technically, it says the length in characters, but I think this one might be correct
// technically, it says the length in characters, but I think this one might be correct
// otherwise it will get complicated, as we'll need to read UTF-8 char by char...
return new String(bytes(in, length, counter), "utf-8");
}