Merge branch 'master' into q-apps

This commit is contained in:
CalDescent 2023-03-17 13:17:47 +00:00
commit 98b0b1932d
9 changed files with 136 additions and 40 deletions

View File

@ -40,6 +40,8 @@ import org.qortal.utils.Base58;
import com.google.common.primitives.Bytes;
import static org.qortal.data.chat.ChatMessage.Encoding;
@Path("/chat")
@Tag(name = "Chat")
public class ChatResource {
@ -73,6 +75,7 @@ public class ChatResource {
@QueryParam("chatreference") String chatReference,
@QueryParam("haschatreference") Boolean hasChatReference,
@QueryParam("sender") String sender,
@QueryParam("encoding") Encoding encoding,
@Parameter(ref = "limit") @QueryParam("limit") Integer limit,
@Parameter(ref = "offset") @QueryParam("offset") Integer offset,
@Parameter(ref = "reverse") @QueryParam("reverse") Boolean reverse) {
@ -109,6 +112,7 @@ public class ChatResource {
hasChatReference,
involvingAddresses,
sender,
encoding,
limit, offset, reverse);
} catch (DataException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
@ -131,7 +135,7 @@ public class ChatResource {
}
)
@ApiErrors({ApiError.INVALID_CRITERIA, ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
public ChatMessage getMessageBySignature(@PathParam("signature") String signature58) {
public ChatMessage getMessageBySignature(@PathParam("signature") String signature58, @QueryParam("encoding") Encoding encoding) {
byte[] signature = Base58.decode(signature58);
try (final Repository repository = RepositoryManager.getRepository()) {
@ -141,7 +145,7 @@ public class ChatResource {
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Message not found");
}
return repository.getChatRepository().toChatMessage(chatTransactionData);
return repository.getChatRepository().toChatMessage(chatTransactionData, encoding);
} catch (DataException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
}
@ -164,12 +168,12 @@ public class ChatResource {
}
)
@ApiErrors({ApiError.INVALID_CRITERIA, ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
public ActiveChats getActiveChats(@PathParam("address") String address) {
public ActiveChats getActiveChats(@PathParam("address") String address, @QueryParam("encoding") Encoding encoding) {
if (address == null || !Crypto.isValidAddress(address))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
try (final Repository repository = RepositoryManager.getRepository()) {
return repository.getChatRepository().getActiveChats(address);
return repository.getChatRepository().getActiveChats(address, encoding);
} catch (DataException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
}

View File

@ -2,7 +2,9 @@ package org.qortal.api.websocket;
import java.io.IOException;
import java.io.StringWriter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.websocket.api.Session;
@ -21,6 +23,8 @@ import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager;
import static org.qortal.data.chat.ChatMessage.Encoding;
@WebSocket
@SuppressWarnings("serial")
public class ActiveChatsWebSocket extends ApiWebSocket {
@ -62,7 +66,9 @@ public class ActiveChatsWebSocket extends ApiWebSocket {
@OnWebSocketMessage
public void onWebSocketMessage(Session session, String message) {
/* ignored */
if (Objects.equals(message, "ping")) {
session.getRemote().sendStringByFuture("pong");
}
}
private void onNotify(Session session, ChatTransactionData chatTransactionData, String ourAddress, AtomicReference<String> previousOutput) {
@ -75,7 +81,7 @@ public class ActiveChatsWebSocket extends ApiWebSocket {
}
try (final Repository repository = RepositoryManager.getRepository()) {
ActiveChats activeChats = repository.getChatRepository().getActiveChats(ourAddress);
ActiveChats activeChats = repository.getChatRepository().getActiveChats(ourAddress, getTargetEncoding(session));
StringWriter stringWriter = new StringWriter();
@ -93,4 +99,12 @@ public class ActiveChatsWebSocket extends ApiWebSocket {
}
}
private Encoding getTargetEncoding(Session session) {
// Default to Base58 if not specified, for backwards support
Map<String, List<String>> queryParams = session.getUpgradeRequest().getParameterMap();
List<String> encodingList = queryParams.get("encoding");
String encoding = (encodingList != null && encodingList.size() == 1) ? encodingList.get(0) : "BASE58";
return Encoding.valueOf(encoding);
}
}

View File

@ -2,10 +2,7 @@ package org.qortal.api.websocket;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketException;
@ -22,6 +19,8 @@ import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager;
import static org.qortal.data.chat.ChatMessage.Encoding;
@WebSocket
@SuppressWarnings("serial")
public class ChatMessagesWebSocket extends ApiWebSocket {
@ -35,6 +34,16 @@ public class ChatMessagesWebSocket extends ApiWebSocket {
@Override
public void onWebSocketConnect(Session session) {
Map<String, List<String>> queryParams = session.getUpgradeRequest().getParameterMap();
Encoding encoding = getTargetEncoding(session);
List<String> limitList = queryParams.get("limit");
Integer limit = (limitList != null && limitList.size() == 1) ? Integer.parseInt(limitList.get(0)) : null;
List<String> offsetList = queryParams.get("offset");
Integer offset = (offsetList != null && offsetList.size() == 1) ? Integer.parseInt(offsetList.get(0)) : null;
List<String> reverseList = queryParams.get("offset");
Boolean reverse = (reverseList != null && reverseList.size() == 1) ? Boolean.getBoolean(reverseList.get(0)) : null;
List<String> txGroupIds = queryParams.get("txGroupId");
if (txGroupIds != null && txGroupIds.size() == 1) {
@ -50,7 +59,8 @@ public class ChatMessagesWebSocket extends ApiWebSocket {
null,
null,
null,
null, null, null);
encoding,
limit, offset, reverse);
sendMessages(session, chatMessages);
} catch (DataException e) {
@ -81,7 +91,8 @@ public class ChatMessagesWebSocket extends ApiWebSocket {
null,
involvingAddresses,
null,
null, null, null);
encoding,
limit, offset, reverse);
sendMessages(session, chatMessages);
} catch (DataException e) {
@ -107,7 +118,9 @@ public class ChatMessagesWebSocket extends ApiWebSocket {
@OnWebSocketMessage
public void onWebSocketMessage(Session session, String message) {
/* ignored */
if (Objects.equals(message, "ping")) {
session.getRemote().sendStringByFuture("pong");
}
}
private void onNotify(Session session, ChatTransactionData chatTransactionData, int txGroupId) {
@ -155,7 +168,7 @@ public class ChatMessagesWebSocket extends ApiWebSocket {
// Convert ChatTransactionData to ChatMessage
ChatMessage chatMessage;
try (final Repository repository = RepositoryManager.getRepository()) {
chatMessage = repository.getChatRepository().toChatMessage(chatTransactionData);
chatMessage = repository.getChatRepository().toChatMessage(chatTransactionData, getTargetEncoding(session));
} catch (DataException e) {
// No output this time?
return;
@ -164,4 +177,12 @@ public class ChatMessagesWebSocket extends ApiWebSocket {
sendMessages(session, Collections.singletonList(chatMessage));
}
private Encoding getTargetEncoding(Session session) {
// Default to Base58 if not specified, for backwards support
Map<String, List<String>> queryParams = session.getUpgradeRequest().getParameterMap();
List<String> encodingList = queryParams.get("encoding");
String encoding = (encodingList != null && encodingList.size() == 1) ? encodingList.get(0) : "BASE58";
return Encoding.valueOf(encoding);
}
}

View File

@ -1,10 +1,15 @@
package org.qortal.data.chat;
import org.bouncycastle.util.encoders.Base64;
import org.qortal.utils.Base58;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import static org.qortal.data.chat.ChatMessage.Encoding;
@XmlAccessorType(XmlAccessType.FIELD)
public class ActiveChats {
@ -18,20 +23,38 @@ public class ActiveChats {
private String sender;
private String senderName;
private byte[] signature;
private byte[] data;
private Encoding encoding;
private String data;
protected GroupChat() {
/* JAXB */
}
public GroupChat(int groupId, String groupName, Long timestamp, String sender, String senderName, byte[] signature, byte[] data) {
public GroupChat(int groupId, String groupName, Long timestamp, String sender, String senderName,
byte[] signature, Encoding encoding, byte[] data) {
this.groupId = groupId;
this.groupName = groupName;
this.timestamp = timestamp;
this.sender = sender;
this.senderName = senderName;
this.signature = signature;
this.data = data;
this.encoding = encoding != null ? encoding : Encoding.BASE58;
if (data != null) {
switch (this.encoding) {
case BASE64:
this.data = Base64.toBase64String(data);
break;
case BASE58:
default:
this.data = Base58.encode(data);
break;
}
}
else {
this.data = null;
}
}
public int getGroupId() {
@ -58,7 +81,7 @@ public class ActiveChats {
return this.signature;
}
public byte[] getData() {
public String getData() {
return this.data;
}
}

View File

@ -1,11 +1,19 @@
package org.qortal.data.chat;
import org.bouncycastle.util.encoders.Base64;
import org.qortal.utils.Base58;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@XmlAccessorType(XmlAccessType.FIELD)
public class ChatMessage {
public enum Encoding {
BASE58,
BASE64
}
// Properties
private long timestamp;
@ -29,7 +37,9 @@ public class ChatMessage {
private byte[] chatReference;
private byte[] data;
private Encoding encoding;
private String data;
private boolean isText;
private boolean isEncrypted;
@ -44,8 +54,8 @@ public class ChatMessage {
// For repository use
public ChatMessage(long timestamp, int txGroupId, byte[] reference, byte[] senderPublicKey, String sender,
String senderName, String recipient, String recipientName, byte[] chatReference, byte[] data,
boolean isText, boolean isEncrypted, byte[] signature) {
String senderName, String recipient, String recipientName, byte[] chatReference,
Encoding encoding, byte[] data, boolean isText, boolean isEncrypted, byte[] signature) {
this.timestamp = timestamp;
this.txGroupId = txGroupId;
this.reference = reference;
@ -55,7 +65,24 @@ public class ChatMessage {
this.recipient = recipient;
this.recipientName = recipientName;
this.chatReference = chatReference;
this.data = data;
this.encoding = encoding != null ? encoding : Encoding.BASE58;
if (data != null) {
switch (this.encoding) {
case BASE64:
this.data = Base64.toBase64String(data);
break;
case BASE58:
default:
this.data = Base58.encode(data);
break;
}
}
else {
this.data = null;
}
this.isText = isText;
this.isEncrypted = isEncrypted;
this.signature = signature;
@ -97,7 +124,7 @@ public class ChatMessage {
return this.chatReference;
}
public byte[] getData() {
public String getData() {
return this.data;
}

View File

@ -6,6 +6,8 @@ import org.qortal.data.chat.ActiveChats;
import org.qortal.data.chat.ChatMessage;
import org.qortal.data.transaction.ChatTransactionData;
import static org.qortal.data.chat.ChatMessage.Encoding;
public interface ChatRepository {
/**
@ -15,10 +17,11 @@ public interface ChatRepository {
*/
public List<ChatMessage> getMessagesMatchingCriteria(Long before, Long after,
Integer txGroupId, byte[] reference, byte[] chatReferenceBytes, Boolean hasChatReference,
List<String> involving, String senderAddress, Integer limit, Integer offset, Boolean reverse) throws DataException;
List<String> involving, String senderAddress, Encoding encoding,
Integer limit, Integer offset, Boolean reverse) throws DataException;
public ChatMessage toChatMessage(ChatTransactionData chatTransactionData) throws DataException;
public ChatMessage toChatMessage(ChatTransactionData chatTransactionData, Encoding encoding) throws DataException;
public ActiveChats getActiveChats(String address) throws DataException;
public ActiveChats getActiveChats(String address, Encoding encoding) throws DataException;
}

View File

@ -14,6 +14,8 @@ import org.qortal.repository.ChatRepository;
import org.qortal.repository.DataException;
import org.qortal.transaction.Transaction.TransactionType;
import static org.qortal.data.chat.ChatMessage.Encoding;
public class HSQLDBChatRepository implements ChatRepository {
protected HSQLDBRepository repository;
@ -24,8 +26,8 @@ public class HSQLDBChatRepository implements ChatRepository {
@Override
public List<ChatMessage> getMessagesMatchingCriteria(Long before, Long after, Integer txGroupId, byte[] referenceBytes,
byte[] chatReferenceBytes, Boolean hasChatReference, List<String> involving, String senderAddress,
Integer limit, Integer offset, Boolean reverse) throws DataException {
byte[] chatReferenceBytes, Boolean hasChatReference, List<String> involving, String senderAddress,
Encoding encoding, Integer limit, Integer offset, Boolean reverse) throws DataException {
// Check args meet expectations
if ((txGroupId != null && involving != null && !involving.isEmpty())
|| (txGroupId == null && (involving == null || involving.size() != 2)))
@ -127,7 +129,7 @@ public class HSQLDBChatRepository implements ChatRepository {
byte[] signature = resultSet.getBytes(13);
ChatMessage chatMessage = new ChatMessage(timestamp, groupId, reference, senderPublicKey, sender,
senderName, recipient, recipientName, chatReference, data, isText, isEncrypted, signature);
senderName, recipient, recipientName, chatReference, encoding, data, isText, isEncrypted, signature);
chatMessages.add(chatMessage);
} while (resultSet.next());
@ -139,7 +141,7 @@ public class HSQLDBChatRepository implements ChatRepository {
}
@Override
public ChatMessage toChatMessage(ChatTransactionData chatTransactionData) throws DataException {
public ChatMessage toChatMessage(ChatTransactionData chatTransactionData, Encoding encoding) throws DataException {
String sql = "SELECT SenderNames.name, RecipientNames.name "
+ "FROM ChatTransactions "
+ "LEFT OUTER JOIN Names AS SenderNames ON SenderNames.owner = sender "
@ -166,21 +168,22 @@ public class HSQLDBChatRepository implements ChatRepository {
byte[] signature = chatTransactionData.getSignature();
return new ChatMessage(timestamp, groupId, reference, senderPublicKey, sender,
senderName, recipient, recipientName, chatReference, data, isText, isEncrypted, signature);
senderName, recipient, recipientName, chatReference, encoding, data,
isText, isEncrypted, signature);
} catch (SQLException e) {
throw new DataException("Unable to fetch convert chat transaction from repository", e);
}
}
@Override
public ActiveChats getActiveChats(String address) throws DataException {
List<GroupChat> groupChats = getActiveGroupChats(address);
public ActiveChats getActiveChats(String address, Encoding encoding) throws DataException {
List<GroupChat> groupChats = getActiveGroupChats(address, encoding);
List<DirectChat> directChats = getActiveDirectChats(address);
return new ActiveChats(groupChats, directChats);
}
private List<GroupChat> getActiveGroupChats(String address) throws DataException {
private List<GroupChat> getActiveGroupChats(String address, Encoding encoding) throws DataException {
// Find groups where address is a member and potential latest message details
String groupsSql = "SELECT group_id, group_name, latest_timestamp, sender, sender_name, signature, data "
+ "FROM GroupMembers "
@ -213,7 +216,7 @@ public class HSQLDBChatRepository implements ChatRepository {
byte[] signature = resultSet.getBytes(6);
byte[] data = resultSet.getBytes(7);
GroupChat groupChat = new GroupChat(groupId, groupName, timestamp, sender, senderName, signature, data);
GroupChat groupChat = new GroupChat(groupId, groupName, timestamp, sender, senderName, signature, encoding, data);
groupChats.add(groupChat);
} while (resultSet.next());
}
@ -247,7 +250,7 @@ public class HSQLDBChatRepository implements ChatRepository {
data = resultSet.getBytes(5);
}
GroupChat groupChat = new GroupChat(0, null, timestamp, sender, senderName, signature, data);
GroupChat groupChat = new GroupChat(0, null, timestamp, sender, senderName, signature, encoding, data);
groupChats.add(groupChat);
} catch (SQLException e) {
throw new DataException("Unable to fetch active group chats from repository", e);

View File

@ -9,6 +9,7 @@ import org.qortal.crosschain.BitcoinACCTv1;
import org.qortal.crypto.Crypto;
import org.qortal.data.account.AccountBalanceData;
import org.qortal.data.account.AccountData;
import org.qortal.data.chat.ChatMessage;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager;
@ -417,7 +418,7 @@ public class RepositoryTests extends Common {
try (final HSQLDBRepository hsqldb = (HSQLDBRepository) RepositoryManager.getRepository()) {
String address = Crypto.toAddress(new byte[32]);
hsqldb.getChatRepository().getActiveChats(address);
hsqldb.getChatRepository().getActiveChats(address, ChatMessage.Encoding.BASE58);
} catch (DataException e) {
fail("HSQLDB bug #1580");
}

View File

@ -8,11 +8,11 @@ if [ -z "$*" ]; then
echo "Usage:"
echo
echo "Host/update data:"
echo "qdata POST [service] [name] PATH [dirpath] <identifier>"
echo "qdata POST [service] [name] STRING [data-string] <identifier>"
echo "qdn POST [service] [name] PATH [dirpath] <identifier>"
echo "qdn POST [service] [name] STRING [data-string] <identifier>"
echo
echo "Fetch data:"
echo "qdata GET [service] [name] <identifier-or-default> <filepath-or-default> <rebuild>"
echo "qdn GET [service] [name] <identifier-or-default> <filepath-or-default> <rebuild>"
echo
echo "Notes:"
echo "- When requesting a resource, please use 'default' to indicate a file with no identifier."