Some code to work with conversations
This commit is contained in:
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
Reference in New Issue
Block a user