mirror of
https://github.com/ChronosX88/Influence-P2P.git
synced 2024-11-25 00:22:17 +00:00
[WIP] Changed architecture: now communications is indirect
This commit is contained in:
parent
627547c648
commit
94643207b7
@ -40,9 +40,9 @@ dependencies {
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3'
|
||||
implementation "androidx.room:room-runtime:2.1.0-alpha04"
|
||||
annotationProcessor "androidx.room:room-compiler:2.1.0-alpha04"
|
||||
implementation('net.tomp2p:tomp2p-all:5.0-Beta8') {
|
||||
exclude group: 'org.mapdb', module: 'mapdb'
|
||||
}
|
||||
implementation('net.tomp2p:tomp2p-all:5.0-Beta8') //{
|
||||
//exclude group: 'org.mapdb', module: 'mapdb'
|
||||
//}
|
||||
implementation 'org.slf4j:slf4j-log4j12:1.7.26'
|
||||
implementation group: 'com.h2database', name: 'h2-mvstore', version: '1.4.197'
|
||||
implementation 'com.google.android.material:material:1.1.0-alpha04'
|
||||
|
@ -2,16 +2,16 @@
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 2,
|
||||
"identityHash": "a24b31a8e1f482a72f55843041945d5b",
|
||||
"identityHash": "81501115d10a6dc46002667323359631",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "messages",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `chatID` TEXT, `sender` TEXT, `timestamp` INTEGER NOT NULL, `text` TEXT, `isSent` INTEGER NOT NULL, `isRead` INTEGER NOT NULL)",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageID` TEXT NOT NULL, `type` INTEGER NOT NULL, `chatID` TEXT, `senderID` TEXT, `username` TEXT, `timestamp` INTEGER NOT NULL, `text` TEXT, `isSent` INTEGER NOT NULL, `isRead` INTEGER NOT NULL, PRIMARY KEY(`messageID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"fieldPath": "messageID",
|
||||
"columnName": "messageID",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
@ -27,8 +27,14 @@
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "sender",
|
||||
"columnName": "sender",
|
||||
"fieldPath": "senderID",
|
||||
"columnName": "senderID",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "username",
|
||||
"columnName": "username",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
@ -59,28 +65,22 @@
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
"messageID"
|
||||
],
|
||||
"autoGenerate": true
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "chats",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `chatID` TEXT, `name` TEXT, `peerAddresses` BLOB, `keyPairID` TEXT)",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`chatID` TEXT NOT NULL, `name` TEXT, `metadataRef` TEXT, `membersRef` TEXT, `bannedUsers` TEXT, `chunkCursor` INTEGER NOT NULL, PRIMARY KEY(`chatID`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "chatID",
|
||||
"columnName": "chatID",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
@ -89,23 +89,35 @@
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "peerAddresses",
|
||||
"columnName": "peerAddresses",
|
||||
"affinity": "BLOB",
|
||||
"fieldPath": "metadataRef",
|
||||
"columnName": "metadataRef",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "keyPairID",
|
||||
"columnName": "keyPairID",
|
||||
"fieldPath": "membersRef",
|
||||
"columnName": "membersRef",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bannedUsers",
|
||||
"columnName": "bannedUsers",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "chunkCursor",
|
||||
"columnName": "chunkCursor",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
"chatID"
|
||||
],
|
||||
"autoGenerate": true
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
@ -114,7 +126,7 @@
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"a24b31a8e1f482a72f55843041945d5b\")"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"81501115d10a6dc46002667323359631\")"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,9 +1,7 @@
|
||||
package io.github.chronosx88.influence.contracts.chatactivity;
|
||||
|
||||
import net.tomp2p.peers.PeerAddress;
|
||||
|
||||
import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
|
||||
|
||||
public interface IChatLogicContract {
|
||||
void sendMessage(PeerAddress address, MessageEntity message);
|
||||
void sendMessage(MessageEntity message);
|
||||
}
|
||||
|
@ -36,7 +36,14 @@ public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.ViewHolder> {
|
||||
}
|
||||
|
||||
public void addMessage(MessageEntity message) {
|
||||
messages.add(message);
|
||||
if(message != null) {
|
||||
for (MessageEntity messageEntity : messages) {
|
||||
if(messageEntity.messageID.equals(message.messageID)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
messages.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void addMessages(List<MessageEntity> messages) {
|
||||
@ -62,7 +69,7 @@ public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.ViewHolder> {
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if(messages.get(position).sender.equals(AppHelper.getPeerID())) {
|
||||
if(messages.get(position).senderID.equals(AppHelper.getPeerID())) {
|
||||
return RIGHT_ITEM;
|
||||
} else {
|
||||
return LEFT_ITEM;
|
||||
|
@ -43,7 +43,7 @@ public class ChatListAdapter extends RecyclerView.Adapter<ChatListAdapter.ChatLi
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ChatListViewHolder holder, int position) {
|
||||
holder.chatName.setText(chatList.get(position).getName());
|
||||
holder.chatName.setText(chatList.get(position).name);
|
||||
holder.onLongClick(position);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,25 @@
|
||||
package io.github.chronosx88.influence.helpers;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import androidx.room.TypeConverter;
|
||||
|
||||
public class Converter {
|
||||
|
||||
@TypeConverter
|
||||
public static ArrayList<String> fromString(String value) {
|
||||
Type listType = new TypeToken<ArrayList<String>>() {}.getType();
|
||||
return new Gson().fromJson(value, listType);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static String fromArrayLisr(ArrayList<String> list) {
|
||||
Gson gson = new Gson();
|
||||
return gson.toJson(list);
|
||||
}
|
||||
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
package io.github.chronosx88.influence.helpers;
|
||||
|
||||
public class JVMShutdownHook extends Thread {
|
||||
StorageMVStore storage;
|
||||
import net.tomp2p.dht.Storage;
|
||||
|
||||
public JVMShutdownHook(StorageMVStore storage) {
|
||||
public class JVMShutdownHook extends Thread {
|
||||
Storage storage;
|
||||
|
||||
public JVMShutdownHook(Storage storage) {
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,7 @@ package io.github.chronosx88.influence.helpers;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import net.tomp2p.peers.PeerAddress;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
|
||||
@ -18,39 +16,36 @@ public class LocalDBWrapper {
|
||||
* Create a chat entry in the local database.
|
||||
* @param chatID Chat ID
|
||||
* @param name Chat name
|
||||
* @param peerAddress Companion's address
|
||||
* @return Successful chat creation (true / false)
|
||||
* @param metadataRef Reference to general chat metadata (key in DHT)
|
||||
* @param membersRef Reference to member list
|
||||
*/
|
||||
public static boolean createChatEntry(String chatID, String name, PeerAddress peerAddress) {
|
||||
List<ChatEntity> chatEntities = AppHelper.getChatDB().chatDao().getChatByChatID(chatID);
|
||||
if (chatEntities.size() > 0) {
|
||||
Log.e(LOG_TAG, "Failed to create chat " + chatID + " because chat exists!");
|
||||
return false;
|
||||
}
|
||||
dbInstance.chatDao().addChat(new ChatEntity(chatID, name, "", Serializer.serialize(peerAddress)));
|
||||
return true;
|
||||
public static void createChatEntry(String chatID, String name, String metadataRef, String membersRef, int chunkID) {
|
||||
dbInstance.chatDao().addChat(new ChatEntity(chatID, name, metadataRef, membersRef, new ArrayList<>(), chunkID));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creating a message entry in the local database
|
||||
* @param type Message type
|
||||
* @param chatID ID of the chat in which need to create a message
|
||||
* @param sender Message sender (username)
|
||||
* @param text Message text (or technical info if technical message type)
|
||||
* @return Message ID (in local DB)
|
||||
* @param username Sender username
|
||||
* @param senderID Sender peer ID
|
||||
* @param timestamp Message timestamp
|
||||
* @param text Message text
|
||||
* @return New message
|
||||
*/
|
||||
public static long createMessageEntry(int type, String chatID, String sender, String text) {
|
||||
public static MessageEntity createMessageEntry(int type, String messageID, String chatID, String username, String senderID, long timestamp, String text, boolean isSent, boolean isRead) {
|
||||
List<ChatEntity> chatEntities = AppHelper.getChatDB().chatDao().getChatByChatID(chatID);
|
||||
if(chatEntities.size() < 1) {
|
||||
Log.e(LOG_TAG, "Failed to create message entry because chat " + chatID + " doesn't exists!");
|
||||
return -1;
|
||||
return null;
|
||||
}
|
||||
MessageEntity message = new MessageEntity(type, chatID, sender, new Date().getTime(), text, false, false);
|
||||
return dbInstance.messageDao().insertMessage(message);
|
||||
MessageEntity message = new MessageEntity(type, messageID, chatID, senderID, username, timestamp, text, isSent, isRead);
|
||||
dbInstance.messageDao().insertMessage(message);
|
||||
return message;
|
||||
}
|
||||
|
||||
public static MessageEntity getMessageByID(long id) {
|
||||
List<MessageEntity> messages = dbInstance.messageDao().getMessageByID(id);
|
||||
public static MessageEntity getMessageByID(String messageID) {
|
||||
List<MessageEntity> messages = dbInstance.messageDao().getMessageByID(messageID);
|
||||
if(messages.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
@ -73,7 +68,11 @@ public class LocalDBWrapper {
|
||||
return chats.get(0);
|
||||
}
|
||||
|
||||
public static void updateChatEntry(long id, boolean isSent) {
|
||||
dbInstance.messageDao().updateMessage(id, isSent);
|
||||
public static void updateChatEntity(ChatEntity chatEntity) {
|
||||
dbInstance.chatDao().updateChat(chatEntity);
|
||||
}
|
||||
|
||||
public static void updateMessage(MessageEntity messageEntity) {
|
||||
dbInstance.messageDao().updateMessage(messageEntity);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
package io.github.chronosx88.influence.helpers;
|
||||
|
||||
public class MessageTypes {
|
||||
public static final int USUAL_MESSAGE = 0x0;
|
||||
}
|
@ -1,26 +1,20 @@
|
||||
package io.github.chronosx88.influence.helpers;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import net.tomp2p.dht.PeerDHT;
|
||||
import net.tomp2p.peers.Number160;
|
||||
import net.tomp2p.peers.Number640;
|
||||
import net.tomp2p.peers.PeerAddress;
|
||||
import net.tomp2p.storage.Data;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.github.chronosx88.influence.contracts.observer.INetworkObserver;
|
||||
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
|
||||
import io.github.chronosx88.influence.helpers.actions.UIActions;
|
||||
import io.github.chronosx88.influence.models.ChatMember;
|
||||
import io.github.chronosx88.influence.models.JoinChatMessage;
|
||||
import io.github.chronosx88.influence.models.NewChatRequestMessage;
|
||||
import io.github.chronosx88.influence.models.SendMessage;
|
||||
import io.github.chronosx88.influence.models.SuccessfullySentMessage;
|
||||
|
||||
public class NetworkHandler implements INetworkObserver {
|
||||
private final static String LOG_TAG = "NetworkHandler";
|
||||
@ -34,55 +28,15 @@ public class NetworkHandler implements INetworkObserver {
|
||||
|
||||
@Override
|
||||
public void handleEvent(Object object) {
|
||||
new Thread(() -> {
|
||||
switch (getMessageAction((String) object)) {
|
||||
case NetworkActions.CREATE_CHAT: {
|
||||
NewChatRequestMessage newChatRequestMessage = gson.fromJson((String) object, NewChatRequestMessage.class);
|
||||
LocalDBWrapper.createChatEntry(newChatRequestMessage.getChatID(), newChatRequestMessage.getSenderID(), newChatRequestMessage.getSenderPeerAddress());
|
||||
handleIncomingChatRequest(newChatRequestMessage.getChatID(), newChatRequestMessage.getSenderPeerAddress());
|
||||
ObservableUtils.notifyUI(UIActions.NEW_CHAT);
|
||||
break;
|
||||
}
|
||||
|
||||
case NetworkActions.SUCCESSFULL_CREATE_CHAT: {
|
||||
NewChatRequestMessage newChatRequestMessage = gson.fromJson((String) object, NewChatRequestMessage.class);
|
||||
LocalDBWrapper.createChatEntry(newChatRequestMessage.getChatID(), newChatRequestMessage.getSenderID(), newChatRequestMessage.getSenderPeerAddress());
|
||||
ObservableUtils.notifyUI(UIActions.SUCCESSFUL_CREATE_CHAT);
|
||||
break;
|
||||
}
|
||||
|
||||
case NetworkActions.NEW_MESSAGE: {
|
||||
SendMessage sendMessage = gson.fromJson((String) object, SendMessage.class);
|
||||
long messageID = LocalDBWrapper.createMessageEntry(sendMessage.getMessageType(), sendMessage.getChatID(), sendMessage.getSenderID(), sendMessage.getText());
|
||||
ObservableUtils.notifyUI(UIActions.MESSAGE_RECEIVED, messageID);
|
||||
sendMessageReceived(sendMessage);
|
||||
break;
|
||||
}
|
||||
|
||||
case NetworkActions.MESSAGE_SENT: {
|
||||
SuccessfullySentMessage successfullySentMessage = gson.fromJson((String) object, SuccessfullySentMessage.class);
|
||||
LocalDBWrapper.updateChatEntry(successfullySentMessage.getMessageID(), true);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public static int getMessageAction(String json) {
|
||||
JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject();
|
||||
return jsonObject.get("action").getAsInt();
|
||||
// Empty
|
||||
}
|
||||
|
||||
|
||||
private void handleIncomingChatRequest(String chatID, PeerAddress chatStarterAddress) {
|
||||
NewChatRequestMessage newChatRequestMessage = new NewChatRequestMessage(chatID, AppHelper.getPeerID(), peerDHT.peerAddress());
|
||||
newChatRequestMessage.setAction(NetworkActions.SUCCESSFULL_CREATE_CHAT);
|
||||
AppHelper.getPeerDHT().peer().sendDirect(chatStarterAddress).object(gson.toJson(newChatRequestMessage)).start().awaitUninterruptibly();
|
||||
}
|
||||
|
||||
public static void handlePendingChats() {
|
||||
public static void handlePendingChatRequests() {
|
||||
Map<Number640, Data> pendingChats = P2PUtils.get(AppHelper.getPeerID() + "_pendingChats");
|
||||
if(pendingChats != null) {
|
||||
for(Map.Entry<Number640, Data> entry : pendingChats.entrySet()) {
|
||||
if (pendingChats != null) {
|
||||
for (Map.Entry<Number640, Data> entry : pendingChats.entrySet()) {
|
||||
NewChatRequestMessage newChatRequestMessage = null;
|
||||
try {
|
||||
newChatRequestMessage = gson.fromJson((String) entry.getValue().object(), NewChatRequestMessage.class);
|
||||
@ -92,73 +46,35 @@ public class NetworkHandler implements INetworkObserver {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
LocalDBWrapper.createChatEntry(
|
||||
newChatRequestMessage.getChatID(),
|
||||
newChatRequestMessage.getSenderID(),
|
||||
newChatRequestMessage.getSenderPeerAddress()
|
||||
);
|
||||
|
||||
NewChatRequestMessage newChatRequestReply = new NewChatRequestMessage(newChatRequestMessage.getChatID(), AppHelper.getPeerID(), peerDHT.peerAddress());
|
||||
newChatRequestReply.setAction(NetworkActions.SUCCESSFULL_CREATE_CHAT);
|
||||
if(P2PUtils.ping(newChatRequestMessage.getSenderPeerAddress())) {
|
||||
peerDHT
|
||||
.peer()
|
||||
.sendDirect(newChatRequestMessage.getSenderPeerAddress())
|
||||
.object(gson.toJson(newChatRequestReply))
|
||||
.start()
|
||||
.awaitUninterruptibly();
|
||||
} else {
|
||||
try {
|
||||
if(P2PUtils.put(newChatRequestMessage.getSenderID() + "_pendingAcceptedChats", newChatRequestReply.getChatID(), new Data(gson.toJson(newChatRequestReply)))) {
|
||||
Log.i(LOG_TAG, "# Successfully put message SUCCESSFULLY_CREATE_CHAT in " + newChatRequestMessage.getSenderID() + "_pendingAcceptedChats, because receiver is offline.");
|
||||
} else {
|
||||
Log.e(LOG_TAG, "# Failed to put message SUCCESSFULLY_CREATE_CHAT in " + newChatRequestMessage.getSenderID() + "_pendingAcceptedChats.");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
peerDHT.remove(Number160.createHash(AppHelper.getPeerID() + "_pendingChats"))
|
||||
.contentKey(Number160.createHash(newChatRequestMessage.getChatID()))
|
||||
.start()
|
||||
.awaitUninterruptibly();
|
||||
}
|
||||
|
||||
ObservableUtils.notifyUI(UIActions.SUCCESSFUL_CREATE_CHAT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void handlePendingAcceptedChats() {
|
||||
Map<Number640, Data> pendingAcceptedChats = P2PUtils.get(AppHelper.getPeerID() + "_pendingAcceptedChats");
|
||||
if(pendingAcceptedChats != null) {
|
||||
for(Map.Entry<Number640, Data> entry : pendingAcceptedChats.entrySet()) {
|
||||
NewChatRequestMessage newChatRequestMessage = null;
|
||||
|
||||
ChatMember chatMember = new ChatMember(AppHelper.getPeerID(), AppHelper.getPeerID());
|
||||
Data putData = null;
|
||||
try {
|
||||
newChatRequestMessage = gson.fromJson((String) entry.getValue().object(), NewChatRequestMessage.class);
|
||||
} catch (ClassNotFoundException | IOException e) {
|
||||
putData = new Data(gson.toJson(chatMember)).protectEntry(keyPairManager.openMainKeyPair());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
/*LocalDBWrapper.createChatEntry(
|
||||
newChatRequestMessage.getMessageID(),
|
||||
P2PUtils.put(newChatRequestMessage.getChatID() + "_members", AppHelper.getPeerID(), putData);
|
||||
|
||||
LocalDBWrapper.createChatEntry(
|
||||
newChatRequestMessage.getChatID(),
|
||||
newChatRequestMessage.getSenderID(),
|
||||
newChatRequestMessage.getSenderPeerAddress()
|
||||
);*/
|
||||
|
||||
Log.i(LOG_TAG, "Chat " + newChatRequestMessage.getChatID() + " successfully accepted!");
|
||||
|
||||
peerDHT.remove(Number160.createHash(AppHelper.getPeerID() + "_pendingAcceptedChats"))
|
||||
.contentKey(Number160.createHash(newChatRequestMessage.getChatID()))
|
||||
.start()
|
||||
.awaitUninterruptibly();
|
||||
newChatRequestMessage.getChatID() + "_metadata",
|
||||
newChatRequestMessage.getChatID() + "_members",
|
||||
newChatRequestMessage.getChunkID()
|
||||
);
|
||||
|
||||
P2PUtils.remove(AppHelper.getPeerID() + "_pendingChats", newChatRequestMessage.getChatID());
|
||||
String messageID = UUID.randomUUID().toString();
|
||||
try {
|
||||
P2PUtils.put(newChatRequestMessage.getChatID() + "_messages", messageID, new Data(gson.toJson(new JoinChatMessage(AppHelper.getPeerID(), AppHelper.getPeerID(), newChatRequestMessage.getChatID(), System.currentTimeMillis()))).protectEntry(keyPairManager.openMainKeyPair()));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
ObservableUtils.notifyUI(UIActions.SUCCESSFUL_CREATE_CHAT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessageReceived(SendMessage sendMessage) {
|
||||
P2PUtils.send(sendMessage.getSenderPeerAddress(), gson.toJson(new SuccessfullySentMessage(AppHelper.getPeerID(), AppHelper.getPeerDHT().peerAddress(), sendMessage.getMessageID())));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -16,7 +16,7 @@ public class ObservableUtils {
|
||||
AppHelper.getObservable().notifyUIObservers(jsonObject);
|
||||
}
|
||||
|
||||
public static void notifyUI(int action, long additional) {
|
||||
public static void notifyUI(int action, int additional) {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.addProperty("action", action);
|
||||
jsonObject.addProperty("additional", additional);
|
||||
|
@ -4,6 +4,7 @@ import com.google.gson.Gson;
|
||||
|
||||
import net.tomp2p.dht.FutureGet;
|
||||
import net.tomp2p.dht.FuturePut;
|
||||
import net.tomp2p.dht.FutureRemove;
|
||||
import net.tomp2p.dht.PeerDHT;
|
||||
import net.tomp2p.futures.FutureDirect;
|
||||
import net.tomp2p.futures.FuturePing;
|
||||
@ -68,4 +69,13 @@ public class P2PUtils {
|
||||
.awaitUninterruptibly();
|
||||
return futureDirect.isSuccess();
|
||||
}
|
||||
|
||||
public static boolean remove(String locationKey, String contentKey) {
|
||||
FutureRemove futureRemove = peerDHT
|
||||
.remove(Number160.createHash(locationKey))
|
||||
.contentKey(contentKey == null ? null : Number160.createHash(contentKey))
|
||||
.start()
|
||||
.awaitUninterruptibly();
|
||||
return futureRemove.isRemoved();
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,15 @@ package io.github.chronosx88.influence.helpers;
|
||||
|
||||
import androidx.room.Database;
|
||||
import androidx.room.RoomDatabase;
|
||||
import androidx.room.TypeConverters;
|
||||
import io.github.chronosx88.influence.models.daos.ChatDao;
|
||||
import io.github.chronosx88.influence.models.daos.MessageDao;
|
||||
import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
|
||||
import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
|
||||
|
||||
@Database(entities = { MessageEntity.class, ChatEntity.class }, version = 2)
|
||||
|
||||
@TypeConverters({Converter.class})
|
||||
public abstract class RoomHelper extends RoomDatabase {
|
||||
public abstract ChatDao chatDao();
|
||||
public abstract MessageDao messageDao();
|
||||
|
@ -0,0 +1,357 @@
|
||||
package io.github.chronosx88.influence.helpers;
|
||||
|
||||
import java.io.File;
|
||||
import java.security.PublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentNavigableMap;
|
||||
|
||||
import net.tomp2p.connection.SignatureFactory;
|
||||
import net.tomp2p.dht.Storage;
|
||||
import net.tomp2p.peers.Number160;
|
||||
import net.tomp2p.peers.Number320;
|
||||
import net.tomp2p.peers.Number480;
|
||||
import net.tomp2p.peers.Number640;
|
||||
import net.tomp2p.storage.Data;
|
||||
import net.tomp2p.storage.DataSerializer;
|
||||
|
||||
import org.mapdb.DB;
|
||||
import org.mapdb.DBMaker;
|
||||
|
||||
public class StorageMapDB implements Storage {
|
||||
// Core
|
||||
final private NavigableMap<Number640, Data> dataMap;
|
||||
// Maintenance
|
||||
final private Map<Number640, Long> timeoutMap;
|
||||
final private ConcurrentNavigableMap<Long, Set<Number640>> timeoutMapRev;
|
||||
// Protection
|
||||
final private Map<Number320, PublicKey> protectedDomainMap;
|
||||
final private Map<Number480, PublicKey> protectedEntryMap;
|
||||
// Responsibility
|
||||
final private Map<Number160, Number160> responsibilityMap;
|
||||
final private Map<Number160, Set<Number160>> responsibilityMapRev;
|
||||
|
||||
final private DB db;
|
||||
|
||||
final private int storageCheckIntervalMillis;
|
||||
|
||||
//for full control
|
||||
public StorageMapDB(DB db, Number160 peerId, File path, SignatureFactory signatureFactory, int storageCheckIntervalMillis) {
|
||||
this.db = db;
|
||||
DataSerializer dataSerializer = new DataSerializer(path, signatureFactory);
|
||||
this.dataMap = db.createTreeMap("dataMap_" + peerId.toString()).valueSerializer(dataSerializer).makeOrGet();
|
||||
this.timeoutMap = db.createTreeMap("timeoutMap_" + peerId.toString()).makeOrGet();
|
||||
this.timeoutMapRev = db.createTreeMap("timeoutMapRev_" + peerId.toString()).makeOrGet();
|
||||
this.protectedDomainMap = db.createTreeMap("protectedDomainMap_" + peerId.toString()).makeOrGet();
|
||||
this.protectedEntryMap = db.createTreeMap("protectedEntryMap_" + peerId.toString()).makeOrGet();
|
||||
this.responsibilityMap = db.createTreeMap("responsibilityMap_" + peerId.toString()).makeOrGet();
|
||||
this.responsibilityMapRev = db.createTreeMap("responsibilityMapRev_" + peerId.toString()).makeOrGet();
|
||||
this.storageCheckIntervalMillis = storageCheckIntervalMillis;
|
||||
}
|
||||
|
||||
//set parameter to a reasonable default
|
||||
public StorageMapDB(Number160 peerId, File path, SignatureFactory signatureFactory) {
|
||||
this(DBMaker.newFileDB(new File(path, "tomp2p")).closeOnJvmShutdown().make(),
|
||||
peerId, path, signatureFactory, 60 * 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data put(Number640 key, Data value) {
|
||||
Data oldData = dataMap.put(key, value);
|
||||
db.commit();
|
||||
return oldData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data get(Number640 key) {
|
||||
return dataMap.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Number640 key) {
|
||||
return dataMap.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int contains(Number640 from, Number640 to) {
|
||||
NavigableMap<Number640, Data> tmp = dataMap.subMap(from, true, to, true);
|
||||
return tmp.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data remove(Number640 key, boolean returnData) {
|
||||
Data retVal = dataMap.remove(key);
|
||||
db.commit();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableMap<Number640, Data> remove(Number640 from, Number640 to) {
|
||||
NavigableMap<Number640, Data> tmp = dataMap.subMap(from, true, to, true);
|
||||
|
||||
// new TreeMap<Number640, Data>(tmp); is not possible as this may lead to no such element exception:
|
||||
//
|
||||
// java.util.NoSuchElementException: null
|
||||
// at java.util.concurrent.ConcurrentSkipListMap$SubMap$SubMapIter.advance(ConcurrentSkipListMap.java:3030) ~[na:1.7.0_60]
|
||||
// at java.util.concurrent.ConcurrentSkipListMap$SubMap$SubMapEntryIterator.next(ConcurrentSkipListMap.java:3100) ~[na:1.7.0_60]
|
||||
// at java.util.concurrent.ConcurrentSkipListMap$SubMap$SubMapEntryIterator.next(ConcurrentSkipListMap.java:3096) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2394) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2344) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.<init>(TreeMap.java:195) ~[na:1.7.0_60]
|
||||
// at net.tomp2p.dht.StorageMemory.subMap(StorageMemory.java:119) ~[classes/:na]
|
||||
//
|
||||
// the reason is that the size in TreeMap.buildFromSorted is stored beforehand, then iteratated. If the size changes,
|
||||
// then you will call next() that returns null and an exception is thrown.
|
||||
final NavigableMap<Number640, Data> retVal = new TreeMap<Number640, Data>();
|
||||
for(final Map.Entry<Number640, Data> entry:tmp.entrySet()) {
|
||||
retVal.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
tmp.clear();
|
||||
db.commit();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableMap<Number640, Data> subMap(Number640 from, Number640 to, int limit, boolean ascending) {
|
||||
NavigableMap<Number640, Data> tmp = dataMap.subMap(from, true, to, true);
|
||||
final NavigableMap<Number640, Data> retVal = new TreeMap<Number640, Data>();
|
||||
if (limit < 0) {
|
||||
|
||||
// new TreeMap<Number640, Data>(tmp); is not possible as this may lead to no such element exception:
|
||||
//
|
||||
// java.util.NoSuchElementException: null
|
||||
// at java.util.concurrent.ConcurrentSkipListMap$SubMap$SubMapIter.advance(ConcurrentSkipListMap.java:3030) ~[na:1.7.0_60]
|
||||
// at java.util.concurrent.ConcurrentSkipListMap$SubMap$SubMapEntryIterator.next(ConcurrentSkipListMap.java:3100) ~[na:1.7.0_60]
|
||||
// at java.util.concurrent.ConcurrentSkipListMap$SubMap$SubMapEntryIterator.next(ConcurrentSkipListMap.java:3096) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2394) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2344) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.<init>(TreeMap.java:195) ~[na:1.7.0_60]
|
||||
// at net.tomp2p.dht.StorageMemory.subMap(StorageMemory.java:119) ~[classes/:na]
|
||||
//
|
||||
// the reason is that the size in TreeMap.buildFromSorted is stored beforehand, then iteratated. If the size changes,
|
||||
// then you will call next() that returns null and an exception is thrown.
|
||||
|
||||
for(final Map.Entry<Number640, Data> entry:(ascending ? tmp : tmp.descendingMap()).entrySet()) {
|
||||
retVal.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
} else {
|
||||
limit = Math.min(limit, tmp.size());
|
||||
Iterator<Map.Entry<Number640, Data>> iterator = ascending ? tmp.entrySet().iterator() : tmp
|
||||
.descendingMap().entrySet().iterator();
|
||||
for (int i = 0; iterator.hasNext() && i < limit; i++) {
|
||||
Map.Entry<Number640, Data> entry = iterator.next();
|
||||
retVal.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableMap<Number640, Data> map() {
|
||||
|
||||
// new TreeMap<Number640, Data>(dataMap); is not possible as this may lead to no such element exception:
|
||||
//
|
||||
// java.util.NoSuchElementException: null
|
||||
// at java.util.concurrent.ConcurrentSkipListMap$SubMap$SubMapIter.advance(ConcurrentSkipListMap.java:3030) ~[na:1.7.0_60]
|
||||
// at java.util.concurrent.ConcurrentSkipListMap$SubMap$SubMapEntryIterator.next(ConcurrentSkipListMap.java:3100) ~[na:1.7.0_60]
|
||||
// at java.util.concurrent.ConcurrentSkipListMap$SubMap$SubMapEntryIterator.next(ConcurrentSkipListMap.java:3096) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2394) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2418) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.buildFromSorted(TreeMap.java:2344) ~[na:1.7.0_60]
|
||||
// at java.util.TreeMap.<init>(TreeMap.java:195) ~[na:1.7.0_60]
|
||||
// at net.tomp2p.dht.StorageMemory.subMap(StorageMemory.java:119) ~[classes/:na]
|
||||
//
|
||||
// the reason is that the size in TreeMap.buildFromSorted is stored beforehand, then iteratated. If the size changes,
|
||||
// then you will call next() that returns null and an exception is thrown.
|
||||
final NavigableMap<Number640, Data> retVal = new TreeMap<Number640, Data>();
|
||||
for(final Map.Entry<Number640, Data> entry:dataMap.entrySet()) {
|
||||
retVal.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
// Maintenance
|
||||
@Override
|
||||
public void addTimeout(Number640 key, long expiration) {
|
||||
Long oldExpiration = timeoutMap.put(key, expiration);
|
||||
putIfAbsent2(expiration, key);
|
||||
if (oldExpiration == null) {
|
||||
return;
|
||||
}
|
||||
removeRevTimeout(key, oldExpiration);
|
||||
db.commit();
|
||||
}
|
||||
|
||||
private void putIfAbsent2(long expiration, Number640 key) {
|
||||
Set<Number640> timeouts = timeoutMapRev.get(expiration);
|
||||
if(timeouts == null) {
|
||||
timeouts = Collections.newSetFromMap(new ConcurrentHashMap<Number640, Boolean>());
|
||||
}
|
||||
timeouts.add(key);
|
||||
timeoutMapRev.put(expiration, timeouts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTimeout(Number640 key) {
|
||||
Long expiration = timeoutMap.remove(key);
|
||||
if (expiration == null) {
|
||||
return;
|
||||
}
|
||||
removeRevTimeout(key, expiration);
|
||||
db.commit();
|
||||
}
|
||||
|
||||
private void removeRevTimeout(Number640 key, Long expiration) {
|
||||
Set<Number640> tmp = timeoutMapRev.get(expiration);
|
||||
if (tmp != null) {
|
||||
tmp.remove(key);
|
||||
if (tmp.isEmpty()) {
|
||||
timeoutMapRev.remove(expiration);
|
||||
} else {
|
||||
timeoutMapRev.put(expiration, tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Number640> subMapTimeout(long to) {
|
||||
SortedMap<Long, Set<Number640>> tmp = timeoutMapRev.subMap(0L, to);
|
||||
Collection<Number640> toRemove = new ArrayList<Number640>();
|
||||
for (Set<Number640> set : tmp.values()) {
|
||||
toRemove.addAll(set);
|
||||
}
|
||||
return toRemove;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Responsibility
|
||||
@Override
|
||||
public Number160 findPeerIDsForResponsibleContent(Number160 locationKey) {
|
||||
return responsibilityMap.get(locationKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Number160> findContentForResponsiblePeerID(Number160 peerID) {
|
||||
return responsibilityMapRev.get(peerID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateResponsibilities(Number160 locationKey, Number160 peerId) {
|
||||
final Number160 oldPeerID = responsibilityMap.put(locationKey, peerId);
|
||||
final boolean hasChanged;
|
||||
if(oldPeerID != null) {
|
||||
if(oldPeerID.equals(peerId)) {
|
||||
hasChanged = false;
|
||||
} else {
|
||||
removeRevResponsibility(oldPeerID, locationKey);
|
||||
hasChanged = true;
|
||||
}
|
||||
} else {
|
||||
hasChanged = true;
|
||||
}
|
||||
Set<Number160> contentIDs = responsibilityMapRev.get(peerId);
|
||||
if(contentIDs == null) {
|
||||
contentIDs = new HashSet<Number160>();
|
||||
}
|
||||
contentIDs.add(locationKey);
|
||||
responsibilityMapRev.put(peerId, contentIDs);
|
||||
db.commit();
|
||||
return hasChanged;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeResponsibility(Number160 locationKey) {
|
||||
final Number160 peerId = responsibilityMap.remove(locationKey);
|
||||
if(peerId != null) {
|
||||
removeRevResponsibility(peerId, locationKey);
|
||||
}
|
||||
db.commit();
|
||||
}
|
||||
|
||||
private void removeRevResponsibility(Number160 peerId, Number160 locationKey) {
|
||||
Set<Number160> contentIDs = responsibilityMapRev.get(peerId);
|
||||
if (contentIDs != null) {
|
||||
contentIDs.remove(locationKey);
|
||||
if (contentIDs.isEmpty()) {
|
||||
responsibilityMapRev.remove(peerId);
|
||||
} else {
|
||||
responsibilityMapRev.put(peerId, contentIDs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Misc
|
||||
@Override
|
||||
public void close() {
|
||||
db.close();
|
||||
}
|
||||
|
||||
// Protection Domain
|
||||
@Override
|
||||
public boolean protectDomain(Number320 key, PublicKey publicKey) {
|
||||
protectedDomainMap.put(key, publicKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDomainProtectedByOthers(Number320 key, PublicKey publicKey) {
|
||||
PublicKey other = protectedDomainMap.get(key);
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
return !other.equals(publicKey);
|
||||
}
|
||||
|
||||
// Protection Entry
|
||||
@Override
|
||||
public boolean protectEntry(Number480 key, PublicKey publicKey) {
|
||||
protectedEntryMap.put(key, publicKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEntryProtectedByOthers(Number480 key, PublicKey publicKey) {
|
||||
PublicKey other = protectedEntryMap.get(key);
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
return !other.equals(publicKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int storageCheckIntervalMillis() {
|
||||
return storageCheckIntervalMillis;
|
||||
}
|
||||
}
|
@ -2,9 +2,7 @@ package io.github.chronosx88.influence.helpers.actions;
|
||||
|
||||
public class NetworkActions {
|
||||
public static final int CREATE_CHAT = 0x0;
|
||||
public static final int NEW_MESSAGE = 0x1;
|
||||
public static final int MESSAGE_SENT = 0x2;
|
||||
public static final int PING = 0x3;
|
||||
public static final int PONG = 0x4;
|
||||
public static final int SUCCESSFULL_CREATE_CHAT = 0x5;
|
||||
public static final int TEXT_MESSAGE = 0x1;
|
||||
public static final int JOIN_CHAT = 0x2;
|
||||
public static final int NEXT_CHUNK_REF = 0x3;
|
||||
}
|
||||
|
@ -1,33 +1,166 @@
|
||||
package io.github.chronosx88.influence.logic;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import net.tomp2p.peers.PeerAddress;
|
||||
import net.tomp2p.peers.Number640;
|
||||
import net.tomp2p.storage.Data;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.github.chronosx88.influence.contracts.chatactivity.IChatLogicContract;
|
||||
import io.github.chronosx88.influence.helpers.AppHelper;
|
||||
import io.github.chronosx88.influence.helpers.LocalDBWrapper;
|
||||
import io.github.chronosx88.influence.helpers.ObservableUtils;
|
||||
import io.github.chronosx88.influence.helpers.P2PUtils;
|
||||
import io.github.chronosx88.influence.models.SendMessage;
|
||||
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
|
||||
import io.github.chronosx88.influence.helpers.actions.UIActions;
|
||||
import io.github.chronosx88.influence.models.JoinChatMessage;
|
||||
import io.github.chronosx88.influence.models.NextChunkReference;
|
||||
import io.github.chronosx88.influence.models.TextMessage;
|
||||
import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
|
||||
import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
|
||||
|
||||
public class ChatLogic implements IChatLogicContract {
|
||||
private static Gson gson = new Gson();
|
||||
private String chatID;
|
||||
private String newMessage = "";
|
||||
private ChatEntity chatEntity;
|
||||
private Thread checkNewMessagesThread = null;
|
||||
|
||||
public ChatLogic(ChatEntity chatEntity) {
|
||||
this.chatEntity = chatEntity;
|
||||
this.chatID = chatEntity.chatID;
|
||||
TimerTask timerTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
checkForNewMessages();
|
||||
}
|
||||
};
|
||||
Timer timer = new Timer();
|
||||
timer.schedule(timerTask, 1, 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(PeerAddress address, MessageEntity message) {
|
||||
public void sendMessage(MessageEntity message) {
|
||||
new Thread(() -> {
|
||||
P2PUtils
|
||||
.send(address, gson.toJson(
|
||||
new SendMessage(
|
||||
AppHelper.getPeerID(),
|
||||
AppHelper.getPeerDHT().peerAddress(),
|
||||
message.id,
|
||||
message.timestamp,
|
||||
message.type,
|
||||
message.chatID,
|
||||
message.text
|
||||
)
|
||||
));
|
||||
Data data = null;
|
||||
try {
|
||||
data = new Data(gson.toJson(new TextMessage(message.senderID, message.messageID, message.chatID, message.username, message.timestamp, message.text, false)));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
P2PUtils.put(chatID + "_messages" + chatEntity.chunkCursor, message.messageID, data);
|
||||
try {
|
||||
P2PUtils.put(chatID + "_newMessage", null, new Data(message.messageID));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
// TODO: put message into DHT if user is offline
|
||||
}
|
||||
|
||||
private void checkForNewMessages() {
|
||||
if(checkNewMessagesThread == null) {
|
||||
checkNewMessagesThread = new Thread(() -> {
|
||||
Map<Number640, Data> data = P2PUtils.get(chatID + "_newMessage");
|
||||
if(data != null) {
|
||||
for(Map.Entry<Number640, Data> entry : data.entrySet()) {
|
||||
String newMessage = null;
|
||||
try {
|
||||
newMessage = (String) entry.getValue().object();
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if(!newMessage.equals(this.newMessage)) {
|
||||
handleNewMessages(chatEntity.chunkCursor);
|
||||
this.newMessage = newMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(!checkNewMessagesThread.isAlive()) {
|
||||
checkNewMessagesThread = new Thread(() -> {
|
||||
Map<Number640, Data> data = P2PUtils.get(chatID + "_newMessage");
|
||||
if(data != null) {
|
||||
for(Map.Entry<Number640, Data> entry : data.entrySet()) {
|
||||
String newMessage = null;
|
||||
try {
|
||||
newMessage = (String) entry.getValue().object();
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if(!newMessage.equals(this.newMessage)) {
|
||||
handleNewMessages(chatEntity.chunkCursor);
|
||||
this.newMessage = newMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
checkNewMessagesThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleNewMessages(int chunkID) {
|
||||
new Thread(() -> {
|
||||
Map<Number640, Data> messages = P2PUtils.get(chatEntity.chatID + "_messages" + chunkID);
|
||||
if (messages != null) {
|
||||
for (Map.Entry<Number640, Data> message : messages.entrySet()) {
|
||||
String json = null;
|
||||
try {
|
||||
json = (String) message.getValue().object();
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
switch (getMessageAction(json)) {
|
||||
case NetworkActions.TEXT_MESSAGE: {
|
||||
TextMessage textMessage = gson.fromJson(json, TextMessage.class);
|
||||
LocalDBWrapper.createMessageEntry(NetworkActions.TEXT_MESSAGE, textMessage.getMessageID(), textMessage.getChatID(), textMessage.getUsername(), textMessage.getSenderID(), textMessage.getTimestamp(), textMessage.getText(), true, false);
|
||||
ObservableUtils.notifyUI(UIActions.MESSAGE_RECEIVED, textMessage.getMessageID());
|
||||
break;
|
||||
}
|
||||
case NetworkActions.JOIN_CHAT: {
|
||||
JoinChatMessage joinChatMessage = gson.fromJson(json, JoinChatMessage.class);
|
||||
LocalDBWrapper.createMessageEntry(NetworkActions.JOIN_CHAT, joinChatMessage.getMessageID(), joinChatMessage.getChatID(), joinChatMessage.getUsername(), joinChatMessage.getSenderID(), joinChatMessage.getTimestamp(), null, true, false);
|
||||
ObservableUtils.notifyUI(UIActions.MESSAGE_RECEIVED, joinChatMessage.getMessageID());
|
||||
break;
|
||||
}
|
||||
case NetworkActions.NEXT_CHUNK_REF: {
|
||||
NextChunkReference nextChunkReference = gson.fromJson(json, NextChunkReference.class);
|
||||
chatEntity.chunkCursor = nextChunkReference.getNextChunkID();
|
||||
LocalDBWrapper.updateChatEntity(chatEntity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(messages.size() > 10) {
|
||||
String messageID = UUID.randomUUID().toString();
|
||||
try {
|
||||
chatEntity.chunkCursor += 1;
|
||||
P2PUtils.put(chatID + "_messages" + chatEntity.chunkCursor, messageID, new Data(gson.toJson(new NextChunkReference(messageID, AppHelper.getPeerID(), AppHelper.getPeerID(), System.currentTimeMillis(), chatEntity.chunkCursor))));
|
||||
LocalDBWrapper.updateChatEntity(chatEntity);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private int getMessageAction(String json) {
|
||||
JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject();
|
||||
return jsonObject.get("action").getAsInt();
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import android.util.Log;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import net.tomp2p.connection.RSASignatureFactory;
|
||||
import net.tomp2p.dht.PeerBuilderDHT;
|
||||
import net.tomp2p.dht.PeerDHT;
|
||||
import net.tomp2p.futures.FutureBootstrap;
|
||||
@ -20,6 +21,7 @@ import net.tomp2p.peers.PeerAddress;
|
||||
import net.tomp2p.relay.tcp.TCPRelayClientConfig;
|
||||
import net.tomp2p.replication.AutoReplication;
|
||||
import net.tomp2p.storage.Data;
|
||||
import net.tomp2p.storage.StorageDisk;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
@ -30,15 +32,20 @@ import java.security.KeyPair;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.spec.DSAPublicKeySpec;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.github.chronosx88.influence.contracts.mainactivity.IMainLogicContract;
|
||||
import io.github.chronosx88.influence.helpers.AppHelper;
|
||||
import io.github.chronosx88.influence.helpers.DSAKey;
|
||||
import io.github.chronosx88.influence.helpers.JVMShutdownHook;
|
||||
import io.github.chronosx88.influence.helpers.KeyPairManager;
|
||||
import io.github.chronosx88.influence.helpers.NetworkHandler;
|
||||
import io.github.chronosx88.influence.helpers.P2PUtils;
|
||||
import io.github.chronosx88.influence.helpers.StorageMVStore;
|
||||
import io.github.chronosx88.influence.helpers.StorageMapDB;
|
||||
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
|
||||
import io.github.chronosx88.influence.helpers.actions.UIActions;
|
||||
import io.github.chronosx88.influence.models.PublicUserProfile;
|
||||
|
||||
@ -54,6 +61,7 @@ public class MainLogic implements IMainLogicContract {
|
||||
private Gson gson;
|
||||
private AutoReplication replication;
|
||||
private KeyPairManager keyPairManager;
|
||||
private Thread checkNewChatsThread = null;
|
||||
|
||||
public MainLogic() {
|
||||
this.context = AppHelper.getContext();
|
||||
@ -77,13 +85,17 @@ public class MainLogic implements IMainLogicContract {
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
StorageMapDB storageDisk = new StorageMapDB(peerID, context.getFilesDir(), new RSASignatureFactory());
|
||||
peerDHT = new PeerBuilderDHT(
|
||||
new PeerBuilder(peerID)
|
||||
.ports(7243)
|
||||
.start()
|
||||
)
|
||||
.storage(new StorageMVStore(peerID, context.getFilesDir()))
|
||||
//.storage(new StorageMVStore(peerID, context.getFilesDir()))
|
||||
.storage(storageDisk)
|
||||
.start();
|
||||
JVMShutdownHook jvmShutdownHook = new JVMShutdownHook(storageDisk);
|
||||
Runtime.getRuntime().addShutdownHook(jvmShutdownHook);
|
||||
try {
|
||||
String bootstrapIP = this.preferences.getString("bootstrapAddress", null);
|
||||
if(bootstrapIP == null) {
|
||||
@ -139,8 +151,22 @@ public class MainLogic implements IMainLogicContract {
|
||||
setReceiveHandler();
|
||||
gson = new Gson();
|
||||
publicProfileToDHT();
|
||||
NetworkHandler.handlePendingChats();
|
||||
NetworkHandler.handlePendingAcceptedChats();
|
||||
NetworkHandler.handlePendingChatRequests();
|
||||
TimerTask timerTask = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if(checkNewChatsThread == null) {
|
||||
checkNewChatsThread = new Thread(NetworkHandler::handlePendingChatRequests);
|
||||
checkNewChatsThread.start();
|
||||
}
|
||||
if(!checkNewChatsThread.isAlive()) {
|
||||
checkNewChatsThread = new Thread(NetworkHandler::handlePendingChatRequests);
|
||||
checkNewChatsThread.start();
|
||||
}
|
||||
}
|
||||
};
|
||||
Timer timer = new Timer();
|
||||
timer.schedule(timerTask, 1, 5000);
|
||||
replication = new AutoReplication(peerDHT.peer()).start();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
|
@ -10,6 +10,7 @@ import net.tomp2p.peers.PeerAddress;
|
||||
import net.tomp2p.storage.Data;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -20,6 +21,7 @@ import io.github.chronosx88.influence.helpers.LocalDBWrapper;
|
||||
import io.github.chronosx88.influence.helpers.ObservableUtils;
|
||||
import io.github.chronosx88.influence.helpers.P2PUtils;
|
||||
import io.github.chronosx88.influence.helpers.actions.UIActions;
|
||||
import io.github.chronosx88.influence.models.ChatMetadata;
|
||||
import io.github.chronosx88.influence.models.NewChatRequestMessage;
|
||||
import io.github.chronosx88.influence.models.PublicUserProfile;
|
||||
|
||||
@ -43,23 +45,29 @@ public class StartChatLogic implements IStartChatLogicContract {
|
||||
ObservableUtils.notifyUI(UIActions.PEER_NOT_EXIST);
|
||||
return;
|
||||
}
|
||||
PeerAddress recipientPeerAddress = getPublicProfile(peerID).getPeerAddress();
|
||||
|
||||
NewChatRequestMessage newChatRequestMessage = new NewChatRequestMessage(UUID.randomUUID().toString(), AppHelper.getPeerID(), peerDHT.peerAddress());
|
||||
if(P2PUtils.ping(recipientPeerAddress)) {
|
||||
peerDHT.peer().sendDirect(recipientPeerAddress).object(gson.toJson(newChatRequestMessage)).start().awaitUninterruptibly();
|
||||
} else {
|
||||
try {
|
||||
if(P2PUtils.put(peerID + "_pendingChats", newChatRequestMessage.getChatID(), new Data(gson.toJson(newChatRequestMessage)))) {
|
||||
Log.i(LOG_TAG, "# Create new offline chat request is successful! ChatID: " + newChatRequestMessage.getChatID());
|
||||
} else {
|
||||
Log.e(LOG_TAG, "# Failed to create offline chat request. ChatID: " + newChatRequestMessage.getChatID());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
NewChatRequestMessage newChatRequestMessage = new NewChatRequestMessage(UUID.randomUUID().toString(), UUID.randomUUID().toString(), AppHelper.getPeerID(), AppHelper.getPeerID(), System.currentTimeMillis(), 0);
|
||||
try {
|
||||
if(P2PUtils.put(peerID + "_pendingChats", newChatRequestMessage.getChatID(), new Data(gson.toJson(newChatRequestMessage)))) {
|
||||
Log.i(LOG_TAG, "# Create new offline chat request is successful! ChatID: " + newChatRequestMessage.getChatID());
|
||||
} else {
|
||||
Log.e(LOG_TAG, "# Failed to create offline chat request. ChatID: " + newChatRequestMessage.getChatID());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
LocalDBWrapper.createChatEntry(newChatRequestMessage.getChatID(), peerID, recipientPeerAddress);
|
||||
|
||||
ArrayList<String> admins = new ArrayList<>();
|
||||
admins.add(AppHelper.getPeerID());
|
||||
Data data = null;
|
||||
try {
|
||||
data = new Data(gson.toJson(new ChatMetadata(peerID, admins, new ArrayList<>())));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
data.protectEntry(keyPairManager.openMainKeyPair());
|
||||
P2PUtils.put(newChatRequestMessage.getChatID() + "_metadata", null, data);
|
||||
LocalDBWrapper.createChatEntry(newChatRequestMessage.getChatID(), peerID, newChatRequestMessage.getChatID() + "_metadata", newChatRequestMessage.getChatID() + "_members", 0);
|
||||
ObservableUtils.notifyUI(UIActions.NEW_CHAT);
|
||||
}).start();
|
||||
}
|
||||
|
@ -9,17 +9,21 @@ import java.io.Serializable;
|
||||
*/
|
||||
public class BasicNetworkMessage implements Serializable {
|
||||
private int action;
|
||||
private String messageID;
|
||||
private String senderID;
|
||||
private PeerAddress senderPeerAddress;
|
||||
private String username;
|
||||
private long timestamp;
|
||||
|
||||
public BasicNetworkMessage() {
|
||||
//
|
||||
}
|
||||
|
||||
public BasicNetworkMessage(int action, String senderID, PeerAddress senderPeerAddress) {
|
||||
public BasicNetworkMessage(int action, String messageID, String senderID, String username, long timestamp) {
|
||||
this.action = action;
|
||||
this.senderID = senderID;
|
||||
this.senderPeerAddress = senderPeerAddress;
|
||||
this.username = username;
|
||||
this.messageID = messageID;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public int getAction() {
|
||||
@ -30,19 +34,15 @@ public class BasicNetworkMessage implements Serializable {
|
||||
return senderID;
|
||||
}
|
||||
|
||||
public PeerAddress getSenderPeerAddress() {
|
||||
return senderPeerAddress;
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setAction(int action) {
|
||||
this.action = action;
|
||||
public String getMessageID() {
|
||||
return messageID;
|
||||
}
|
||||
|
||||
public void setSenderID(String senderID) {
|
||||
this.senderID = senderID;
|
||||
}
|
||||
|
||||
public void setSenderPeerAddress(PeerAddress senderPeerAddress) {
|
||||
this.senderPeerAddress = senderPeerAddress;
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
package io.github.chronosx88.influence.models;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class ChatMember implements Serializable {
|
||||
private String username;
|
||||
private String peerID;
|
||||
|
||||
public ChatMember(String username, String peerID) {
|
||||
this.username = username;
|
||||
this.peerID = peerID;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getPeerID() {
|
||||
return peerID;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package io.github.chronosx88.influence.models;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ChatMetadata implements Serializable {
|
||||
private String name;
|
||||
private ArrayList<String> admins;
|
||||
private ArrayList<String> banned;
|
||||
|
||||
public ChatMetadata(String name, ArrayList<String> admins, ArrayList<String> banned) {
|
||||
this.name = name;
|
||||
this.admins = admins;
|
||||
this.banned = banned;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public ArrayList<String> getAdmins() {
|
||||
return admins;
|
||||
}
|
||||
|
||||
public ArrayList<String> getBanned() {
|
||||
return banned;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setAdmins(ArrayList<String> admins) {
|
||||
this.admins = admins;
|
||||
}
|
||||
|
||||
public void setBanned(ArrayList<String> banned) {
|
||||
this.banned = banned;
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package io.github.chronosx88.influence.models;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
|
||||
|
||||
public class JoinChatMessage extends BasicNetworkMessage implements Serializable {
|
||||
private String chatID;
|
||||
|
||||
public JoinChatMessage(String senderID, String username, String chatID, long timestamp) {
|
||||
super(NetworkActions.JOIN_CHAT, UUID.randomUUID().toString(), senderID, username, timestamp);
|
||||
this.chatID = chatID;
|
||||
}
|
||||
|
||||
public String getChatID() {
|
||||
return chatID;
|
||||
}
|
||||
}
|
@ -1,21 +1,24 @@
|
||||
package io.github.chronosx88.influence.models;
|
||||
|
||||
import net.tomp2p.peers.PeerAddress;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
|
||||
|
||||
public class NewChatRequestMessage extends BasicNetworkMessage implements Serializable {
|
||||
private String chatID;
|
||||
private int chunkID;
|
||||
|
||||
public NewChatRequestMessage(String chatID, String senderID, PeerAddress senderPeerAddress) {
|
||||
super(NetworkActions.CREATE_CHAT, senderID, senderPeerAddress);
|
||||
public NewChatRequestMessage(String messageID, String chatID, String senderID, String username, long timestamp, int chunkID) {
|
||||
super(NetworkActions.CREATE_CHAT, messageID, senderID, username, timestamp);
|
||||
this.chatID = chatID;
|
||||
this.chunkID = chunkID;
|
||||
}
|
||||
|
||||
public String getChatID() {
|
||||
return chatID;
|
||||
}
|
||||
|
||||
public int getChunkID() {
|
||||
return chunkID;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
package io.github.chronosx88.influence.models;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
|
||||
|
||||
public class NextChunkReference extends BasicNetworkMessage implements Serializable {
|
||||
private int nextChunkID;
|
||||
|
||||
public NextChunkReference(String messageID, String senderID, String username, long timestamp, int nextChunkID) {
|
||||
super(NetworkActions.NEXT_CHUNK_REF, messageID, senderID, username, timestamp);
|
||||
this.nextChunkID = nextChunkID;
|
||||
}
|
||||
|
||||
public int getNextChunkID() {
|
||||
return nextChunkID;
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
package io.github.chronosx88.influence.models;
|
||||
|
||||
import net.tomp2p.peers.PeerAddress;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
|
||||
|
||||
public class SendMessage extends BasicNetworkMessage implements Serializable {
|
||||
private long messageID;
|
||||
private long timestamp;
|
||||
private int messageType;
|
||||
private String chatID;
|
||||
private String text;
|
||||
|
||||
public SendMessage(String senderID, PeerAddress senderPeerAddress, long messageID,long timestamp, int messageType, String chatID, String text) {
|
||||
super(NetworkActions.NEW_MESSAGE, senderID, senderPeerAddress);
|
||||
this.messageID = messageID;
|
||||
this.timestamp = timestamp;
|
||||
this.messageType = messageType;
|
||||
this.chatID = chatID;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public int getMessageType() {
|
||||
return messageType;
|
||||
}
|
||||
|
||||
public String getChatID() {
|
||||
return chatID;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public void setMessageType(int messageType) {
|
||||
this.messageType = messageType;
|
||||
}
|
||||
|
||||
public void setChatID(String chatID) {
|
||||
this.chatID = chatID;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public long getMessageID() {
|
||||
return messageID;
|
||||
}
|
||||
|
||||
public void setMessageID(long messageID) {
|
||||
this.messageID = messageID;
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package io.github.chronosx88.influence.models;
|
||||
|
||||
import net.tomp2p.peers.PeerAddress;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
|
||||
|
||||
public class SuccessfullySentMessage extends BasicNetworkMessage implements Serializable {
|
||||
private long messageID;
|
||||
|
||||
public SuccessfullySentMessage(String senderID, PeerAddress senderPeerAddress, long messageID) {
|
||||
super(NetworkActions.MESSAGE_SENT, senderID, senderPeerAddress);
|
||||
this.messageID = messageID;
|
||||
}
|
||||
|
||||
public long getMessageID() {
|
||||
return messageID;
|
||||
}
|
||||
|
||||
public void setMessageID(long messageID) {
|
||||
this.messageID = messageID;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package io.github.chronosx88.influence.models;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
|
||||
|
||||
public class TextMessage extends BasicNetworkMessage implements Serializable {
|
||||
private String chatID; // Chat ID
|
||||
private String text; // Message text
|
||||
private boolean isRead; // Message Read Indicator
|
||||
|
||||
public TextMessage(String senderID, String messageID, String chatID, String username, long timestamp, String text, boolean isRead) {
|
||||
super(NetworkActions.TEXT_MESSAGE, messageID, senderID, username, timestamp);
|
||||
this.chatID = chatID;
|
||||
this.text = text;
|
||||
this.isRead = isRead;
|
||||
}
|
||||
|
||||
public String getChatID() {
|
||||
return chatID;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public boolean isRead() {
|
||||
return isRead;
|
||||
}
|
||||
}
|
@ -4,12 +4,14 @@ import java.util.List;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
|
||||
|
||||
@Dao
|
||||
public interface ChatDao {
|
||||
@Insert
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
void addChat(ChatEntity chatEntity);
|
||||
|
||||
@Query("DELETE FROM chats WHERE chatID = :chatID")
|
||||
@ -21,6 +23,6 @@ public interface ChatDao {
|
||||
@Query("SELECT * FROM chats WHERE chatID = :chatID")
|
||||
List<ChatEntity> getChatByChatID(String chatID);
|
||||
|
||||
@Query("SELECT * FROM chats WHERE id = :id")
|
||||
List<ChatEntity> getChatByID(String id);
|
||||
@Update
|
||||
void updateChat(ChatEntity chat);
|
||||
}
|
||||
|
@ -4,16 +4,18 @@ import java.util.List;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
|
||||
|
||||
@Dao
|
||||
public interface MessageDao {
|
||||
@Insert
|
||||
long insertMessage(MessageEntity chatModel);
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
void insertMessage(MessageEntity chatModel);
|
||||
|
||||
@Query("DELETE FROM messages WHERE id = :msgID")
|
||||
void deleteMessage(long msgID);
|
||||
@Query("DELETE FROM messages WHERE messageID = :messageID")
|
||||
void deleteMessage(String messageID);
|
||||
|
||||
@Query("DELETE FROM messages WHERE chatID = :chatID")
|
||||
void deleteMessagesByChatID(String chatID);
|
||||
@ -21,12 +23,9 @@ public interface MessageDao {
|
||||
@Query("SELECT * FROM messages WHERE chatID = :chatID")
|
||||
List<MessageEntity> getMessagesByChatID(String chatID);
|
||||
|
||||
@Query("SELECT * FROM messages WHERE id = :id")
|
||||
List<MessageEntity> getMessageByID(long id);
|
||||
@Query("SELECT * FROM messages WHERE messageID = :messageID")
|
||||
List<MessageEntity> getMessageByID(String messageID);
|
||||
|
||||
@Query("UPDATE messages SET isSent = :isSent WHERE id = :msgID")
|
||||
void updateMessage(long msgID, boolean isSent);
|
||||
|
||||
@Query("UPDATE messages SET text = :text WHERE id = :msgID")
|
||||
void updateMessage(long msgID, String text);
|
||||
@Update
|
||||
void updateMessage(MessageEntity message);
|
||||
}
|
||||
|
@ -1,39 +1,27 @@
|
||||
package io.github.chronosx88.influence.models.roomEntities;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
@Entity(tableName = "chats")
|
||||
public class ChatEntity {
|
||||
@PrimaryKey(autoGenerate = true) public int id;
|
||||
@ColumnInfo public String chatID;
|
||||
@PrimaryKey @NonNull public String chatID;
|
||||
@ColumnInfo public String name;
|
||||
@ColumnInfo public byte[] peerAddresses;
|
||||
@ColumnInfo public String keyPairID;
|
||||
@ColumnInfo public String metadataRef;
|
||||
@ColumnInfo public String membersRef;
|
||||
@ColumnInfo public ArrayList<String> bannedUsers;
|
||||
@ColumnInfo public int chunkCursor;
|
||||
|
||||
public ChatEntity(String chatID, String name, String keyPairID, byte[] peerAddresses) {
|
||||
public ChatEntity(@NonNull String chatID, String name, String metadataRef, String membersRef, ArrayList<String> bannedUsers, int chunkCursor) {
|
||||
this.chatID = chatID;
|
||||
this.name = name;
|
||||
this.peerAddresses = peerAddresses;
|
||||
this.keyPairID = keyPairID;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getKeyPairID() {
|
||||
return keyPairID;
|
||||
}
|
||||
|
||||
public byte[] getPeerAddress() { return peerAddresses; }
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getChatID() {
|
||||
return chatID;
|
||||
this.metadataRef = metadataRef;
|
||||
this.membersRef = membersRef;
|
||||
this.bannedUsers = bannedUsers;
|
||||
this.chunkCursor = chunkCursor;
|
||||
}
|
||||
}
|
||||
|
@ -7,19 +7,22 @@ import androidx.room.PrimaryKey;
|
||||
|
||||
@Entity(tableName = "messages")
|
||||
public class MessageEntity {
|
||||
@PrimaryKey(autoGenerate = true) public long id;
|
||||
@ColumnInfo public int type;
|
||||
@ColumnInfo public String chatID;
|
||||
@ColumnInfo public String sender;
|
||||
@ColumnInfo public long timestamp;
|
||||
@ColumnInfo public String text;
|
||||
@ColumnInfo public boolean isSent;
|
||||
@ColumnInfo public boolean isRead;
|
||||
@PrimaryKey @NonNull public String messageID; // Global message ID
|
||||
@ColumnInfo public int type; // Message type
|
||||
@ColumnInfo public String chatID; // Chat ID
|
||||
@ColumnInfo public String senderID; // PeerID
|
||||
@ColumnInfo public String username; // Username
|
||||
@ColumnInfo public long timestamp; // Timestamp
|
||||
@ColumnInfo public String text; // Message text
|
||||
@ColumnInfo public boolean isSent; // Send status indicator
|
||||
@ColumnInfo public boolean isRead; // Message Read Indicator
|
||||
|
||||
public MessageEntity(int type, String chatID, String sender, long timestamp, String text, boolean isSent, boolean isRead) {
|
||||
public MessageEntity(int type, String messageID, String chatID, String senderID, String username, long timestamp, String text, boolean isSent, boolean isRead) {
|
||||
this.type = type;
|
||||
this.messageID = messageID;
|
||||
this.chatID = chatID;
|
||||
this.sender = sender;
|
||||
this.senderID = senderID;
|
||||
this.username = username;
|
||||
this.timestamp = timestamp;
|
||||
this.text = text;
|
||||
this.isSent = isSent;
|
||||
@ -29,6 +32,6 @@ public class MessageEntity {
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return id + "/" + chatID + "/" + type + "/" + sender + "/" + text;
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,9 @@ package io.github.chronosx88.influence.presenters;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import net.tomp2p.peers.PeerAddress;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.github.chronosx88.influence.contracts.chatactivity.IChatLogicContract;
|
||||
import io.github.chronosx88.influence.contracts.chatactivity.IChatPresenterContract;
|
||||
@ -14,33 +13,33 @@ import io.github.chronosx88.influence.contracts.chatactivity.IChatViewContract;
|
||||
import io.github.chronosx88.influence.contracts.observer.IObserver;
|
||||
import io.github.chronosx88.influence.helpers.AppHelper;
|
||||
import io.github.chronosx88.influence.helpers.LocalDBWrapper;
|
||||
import io.github.chronosx88.influence.helpers.MessageTypes;
|
||||
import io.github.chronosx88.influence.helpers.Serializer;
|
||||
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
|
||||
import io.github.chronosx88.influence.helpers.actions.UIActions;
|
||||
import io.github.chronosx88.influence.logic.ChatLogic;
|
||||
import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
|
||||
import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
|
||||
|
||||
public class ChatPresenter implements IChatPresenterContract, IObserver {
|
||||
private IChatLogicContract logic;
|
||||
private IChatViewContract view;
|
||||
private PeerAddress receiverAddress;
|
||||
private ChatEntity chatEntity;
|
||||
private String chatID;
|
||||
private Gson gson;
|
||||
|
||||
public ChatPresenter(IChatViewContract view, String chatID) {
|
||||
this.logic = new ChatLogic();
|
||||
this.logic = new ChatLogic(LocalDBWrapper.getChatByChatID(chatID));
|
||||
this.view = view;
|
||||
this.chatID = chatID;
|
||||
this.receiverAddress = (PeerAddress) Serializer.deserialize(LocalDBWrapper.getChatByChatID(chatID).getPeerAddress());
|
||||
this.chatEntity = LocalDBWrapper.getChatByChatID(chatID);
|
||||
|
||||
AppHelper.getObservable().register(this);
|
||||
gson = new Gson();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(String text) {
|
||||
long messageID = LocalDBWrapper.createMessageEntry(MessageTypes.USUAL_MESSAGE, chatID, AppHelper.getPeerID(), text);
|
||||
MessageEntity message = LocalDBWrapper.getMessageByID(messageID);
|
||||
logic.sendMessage(receiverAddress, message);
|
||||
MessageEntity message = LocalDBWrapper.createMessageEntry(NetworkActions.TEXT_MESSAGE, UUID.randomUUID().toString(), chatID, AppHelper.getPeerID(), AppHelper.getPeerID(), System.currentTimeMillis(), text, false, false);
|
||||
logic.sendMessage(message);
|
||||
view.updateMessageList(message);
|
||||
}
|
||||
|
||||
@ -48,7 +47,7 @@ public class ChatPresenter implements IChatPresenterContract, IObserver {
|
||||
public void handleEvent(JsonObject object) {
|
||||
switch (object.get("action").getAsInt()) {
|
||||
case UIActions.MESSAGE_RECEIVED: {
|
||||
MessageEntity messageEntity = LocalDBWrapper.getMessageByID(object.get("additional").getAsInt());
|
||||
MessageEntity messageEntity = LocalDBWrapper.getMessageByID(object.get("additional").getAsString());
|
||||
view.updateMessageList(messageEntity);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user