From 94643207b70bd76d80ab2b5201e21d7d09303406 Mon Sep 17 00:00:00 2001 From: ChronosX88 Date: Mon, 1 Apr 2019 21:52:15 +0400 Subject: [PATCH] [WIP] Changed architecture: now communications is indirect --- app/build.gradle | 6 +- .../2.json | 62 +-- .../chatactivity/IChatLogicContract.java | 4 +- .../influence/helpers/ChatAdapter.java | 11 +- .../influence/helpers/ChatListAdapter.java | 2 +- .../influence/helpers/Converter.java | 25 ++ .../influence/helpers/JVMShutdownHook.java | 8 +- .../influence/helpers/LocalDBWrapper.java | 47 ++- .../influence/helpers/MessageTypes.java | 5 - .../influence/helpers/NetworkHandler.java | 140 ++----- .../influence/helpers/ObservableUtils.java | 2 +- .../influence/helpers/P2PUtils.java | 10 + .../influence/helpers/RoomHelper.java | 3 + .../influence/helpers/StorageMapDB.java | 357 ++++++++++++++++++ .../helpers/actions/NetworkActions.java | 8 +- .../chronosx88/influence/logic/ChatLogic.java | 165 +++++++- .../chronosx88/influence/logic/MainLogic.java | 32 +- .../influence/logic/StartChatLogic.java | 36 +- .../influence/models/BasicNetworkMessage.java | 26 +- .../influence/models/ChatMember.java | 21 ++ .../influence/models/ChatMetadata.java | 40 ++ .../influence/models/JoinChatMessage.java | 19 + .../models/NewChatRequestMessage.java | 13 +- .../influence/models/NextChunkReference.java | 18 + .../influence/models/SendMessage.java | 64 ---- .../models/SuccessfullySentMessage.java | 24 -- .../influence/models/TextMessage.java | 30 ++ .../influence/models/daos/ChatDao.java | 8 +- .../influence/models/daos/MessageDao.java | 21 +- .../models/roomEntities/ChatEntity.java | 38 +- .../models/roomEntities/MessageEntity.java | 25 +- .../influence/presenters/ChatPresenter.java | 21 +- 32 files changed, 907 insertions(+), 384 deletions(-) create mode 100644 app/src/main/java/io/github/chronosx88/influence/helpers/Converter.java delete mode 100644 app/src/main/java/io/github/chronosx88/influence/helpers/MessageTypes.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/helpers/StorageMapDB.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/models/ChatMember.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/models/ChatMetadata.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/models/JoinChatMessage.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/models/NextChunkReference.java delete mode 100644 app/src/main/java/io/github/chronosx88/influence/models/SendMessage.java delete mode 100644 app/src/main/java/io/github/chronosx88/influence/models/SuccessfullySentMessage.java create mode 100644 app/src/main/java/io/github/chronosx88/influence/models/TextMessage.java diff --git a/app/build.gradle b/app/build.gradle index 95256fc..91c0686 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/schemas/io.github.chronosx88.influence.helpers.RoomHelper/2.json b/app/schemas/io.github.chronosx88.influence.helpers.RoomHelper/2.json index 4454e73..31a7610 100644 --- a/app/schemas/io.github.chronosx88.influence.helpers.RoomHelper/2.json +++ b/app/schemas/io.github.chronosx88.influence.helpers.RoomHelper/2.json @@ -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\")" ] } } \ No newline at end of file diff --git a/app/src/main/java/io/github/chronosx88/influence/contracts/chatactivity/IChatLogicContract.java b/app/src/main/java/io/github/chronosx88/influence/contracts/chatactivity/IChatLogicContract.java index c1484d2..5048b96 100644 --- a/app/src/main/java/io/github/chronosx88/influence/contracts/chatactivity/IChatLogicContract.java +++ b/app/src/main/java/io/github/chronosx88/influence/contracts/chatactivity/IChatLogicContract.java @@ -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); } diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/ChatAdapter.java b/app/src/main/java/io/github/chronosx88/influence/helpers/ChatAdapter.java index 4ead9c5..80f6a8c 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/ChatAdapter.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/ChatAdapter.java @@ -36,7 +36,14 @@ public class ChatAdapter extends RecyclerView.Adapter { } 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 messages) { @@ -62,7 +69,7 @@ public class ChatAdapter extends RecyclerView.Adapter { @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; diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/ChatListAdapter.java b/app/src/main/java/io/github/chronosx88/influence/helpers/ChatListAdapter.java index 5593f18..4fe5f9f 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/ChatListAdapter.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/ChatListAdapter.java @@ -43,7 +43,7 @@ public class ChatListAdapter extends RecyclerView.Adapter fromString(String value) { + Type listType = new TypeToken>() {}.getType(); + return new Gson().fromJson(value, listType); + } + + @TypeConverter + public static String fromArrayLisr(ArrayList list) { + Gson gson = new Gson(); + return gson.toJson(list); + } + +} diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/JVMShutdownHook.java b/app/src/main/java/io/github/chronosx88/influence/helpers/JVMShutdownHook.java index b236c6e..008a28e 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/JVMShutdownHook.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/JVMShutdownHook.java @@ -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; } diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/LocalDBWrapper.java b/app/src/main/java/io/github/chronosx88/influence/helpers/LocalDBWrapper.java index ca4472f..8b5a035 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/LocalDBWrapper.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/LocalDBWrapper.java @@ -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 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 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 messages = dbInstance.messageDao().getMessageByID(id); + public static MessageEntity getMessageByID(String messageID) { + List 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); } } diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/MessageTypes.java b/app/src/main/java/io/github/chronosx88/influence/helpers/MessageTypes.java deleted file mode 100644 index 579ae88..0000000 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/MessageTypes.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.github.chronosx88.influence.helpers; - -public class MessageTypes { - public static final int USUAL_MESSAGE = 0x0; -} diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/NetworkHandler.java b/app/src/main/java/io/github/chronosx88/influence/helpers/NetworkHandler.java index 3a15e55..4cf0f54 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/NetworkHandler.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/NetworkHandler.java @@ -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 pendingChats = P2PUtils.get(AppHelper.getPeerID() + "_pendingChats"); - if(pendingChats != null) { - for(Map.Entry entry : pendingChats.entrySet()) { + if (pendingChats != null) { + for (Map.Entry 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 pendingAcceptedChats = P2PUtils.get(AppHelper.getPeerID() + "_pendingAcceptedChats"); - if(pendingAcceptedChats != null) { - for(Map.Entry 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()))); - } -} + +} \ No newline at end of file diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/ObservableUtils.java b/app/src/main/java/io/github/chronosx88/influence/helpers/ObservableUtils.java index 182bda3..b3e0466 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/ObservableUtils.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/ObservableUtils.java @@ -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); diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/P2PUtils.java b/app/src/main/java/io/github/chronosx88/influence/helpers/P2PUtils.java index bd47dd1..2d4d4a1 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/P2PUtils.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/P2PUtils.java @@ -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(); + } } diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/RoomHelper.java b/app/src/main/java/io/github/chronosx88/influence/helpers/RoomHelper.java index 24e0a24..baeff7c 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/RoomHelper.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/RoomHelper.java @@ -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(); diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/StorageMapDB.java b/app/src/main/java/io/github/chronosx88/influence/helpers/StorageMapDB.java new file mode 100644 index 0000000..4cb4087 --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/StorageMapDB.java @@ -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 dataMap; + // Maintenance + final private Map timeoutMap; + final private ConcurrentNavigableMap> timeoutMapRev; + // Protection + final private Map protectedDomainMap; + final private Map protectedEntryMap; + // Responsibility + final private Map responsibilityMap; + final private Map> 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 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 remove(Number640 from, Number640 to) { + NavigableMap tmp = dataMap.subMap(from, true, to, true); + + // new TreeMap(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.(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 retVal = new TreeMap(); + for(final Map.Entry entry:tmp.entrySet()) { + retVal.put(entry.getKey(), entry.getValue()); + } + + tmp.clear(); + db.commit(); + return retVal; + } + + @Override + public NavigableMap subMap(Number640 from, Number640 to, int limit, boolean ascending) { + NavigableMap tmp = dataMap.subMap(from, true, to, true); + final NavigableMap retVal = new TreeMap(); + if (limit < 0) { + + // new TreeMap(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.(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 entry:(ascending ? tmp : tmp.descendingMap()).entrySet()) { + retVal.put(entry.getKey(), entry.getValue()); + } + } else { + limit = Math.min(limit, tmp.size()); + Iterator> iterator = ascending ? tmp.entrySet().iterator() : tmp + .descendingMap().entrySet().iterator(); + for (int i = 0; iterator.hasNext() && i < limit; i++) { + Map.Entry entry = iterator.next(); + retVal.put(entry.getKey(), entry.getValue()); + } + } + return retVal; + } + + @Override + public NavigableMap map() { + + // new TreeMap(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.(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 retVal = new TreeMap(); + for(final Map.Entry 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 timeouts = timeoutMapRev.get(expiration); + if(timeouts == null) { + timeouts = Collections.newSetFromMap(new ConcurrentHashMap()); + } + 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 tmp = timeoutMapRev.get(expiration); + if (tmp != null) { + tmp.remove(key); + if (tmp.isEmpty()) { + timeoutMapRev.remove(expiration); + } else { + timeoutMapRev.put(expiration, tmp); + } + } + } + + @Override + public Collection subMapTimeout(long to) { + SortedMap> tmp = timeoutMapRev.subMap(0L, to); + Collection toRemove = new ArrayList(); + for (Set set : tmp.values()) { + toRemove.addAll(set); + } + return toRemove; + } + + + + // Responsibility + @Override + public Number160 findPeerIDsForResponsibleContent(Number160 locationKey) { + return responsibilityMap.get(locationKey); + } + + @Override + public Collection 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 contentIDs = responsibilityMapRev.get(peerId); + if(contentIDs == null) { + contentIDs = new HashSet(); + } + 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 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; + } +} diff --git a/app/src/main/java/io/github/chronosx88/influence/helpers/actions/NetworkActions.java b/app/src/main/java/io/github/chronosx88/influence/helpers/actions/NetworkActions.java index 3ab13f0..d691620 100644 --- a/app/src/main/java/io/github/chronosx88/influence/helpers/actions/NetworkActions.java +++ b/app/src/main/java/io/github/chronosx88/influence/helpers/actions/NetworkActions.java @@ -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; } diff --git a/app/src/main/java/io/github/chronosx88/influence/logic/ChatLogic.java b/app/src/main/java/io/github/chronosx88/influence/logic/ChatLogic.java index ab361f0..5473f44 100644 --- a/app/src/main/java/io/github/chronosx88/influence/logic/ChatLogic.java +++ b/app/src/main/java/io/github/chronosx88/influence/logic/ChatLogic.java @@ -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 data = P2PUtils.get(chatID + "_newMessage"); + if(data != null) { + for(Map.Entry 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 data = P2PUtils.get(chatID + "_newMessage"); + if(data != null) { + for(Map.Entry 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 messages = P2PUtils.get(chatEntity.chatID + "_messages" + chunkID); + if (messages != null) { + for (Map.Entry 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(); } } diff --git a/app/src/main/java/io/github/chronosx88/influence/logic/MainLogic.java b/app/src/main/java/io/github/chronosx88/influence/logic/MainLogic.java index 112c621..2704863 100644 --- a/app/src/main/java/io/github/chronosx88/influence/logic/MainLogic.java +++ b/app/src/main/java/io/github/chronosx88/influence/logic/MainLogic.java @@ -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(); diff --git a/app/src/main/java/io/github/chronosx88/influence/logic/StartChatLogic.java b/app/src/main/java/io/github/chronosx88/influence/logic/StartChatLogic.java index 196c63a..4994606 100644 --- a/app/src/main/java/io/github/chronosx88/influence/logic/StartChatLogic.java +++ b/app/src/main/java/io/github/chronosx88/influence/logic/StartChatLogic.java @@ -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 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(); } diff --git a/app/src/main/java/io/github/chronosx88/influence/models/BasicNetworkMessage.java b/app/src/main/java/io/github/chronosx88/influence/models/BasicNetworkMessage.java index 0ea1fcc..df873a9 100644 --- a/app/src/main/java/io/github/chronosx88/influence/models/BasicNetworkMessage.java +++ b/app/src/main/java/io/github/chronosx88/influence/models/BasicNetworkMessage.java @@ -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; } } diff --git a/app/src/main/java/io/github/chronosx88/influence/models/ChatMember.java b/app/src/main/java/io/github/chronosx88/influence/models/ChatMember.java new file mode 100644 index 0000000..133d584 --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/models/ChatMember.java @@ -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; + } +} diff --git a/app/src/main/java/io/github/chronosx88/influence/models/ChatMetadata.java b/app/src/main/java/io/github/chronosx88/influence/models/ChatMetadata.java new file mode 100644 index 0000000..54e2141 --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/models/ChatMetadata.java @@ -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 admins; + private ArrayList banned; + + public ChatMetadata(String name, ArrayList admins, ArrayList banned) { + this.name = name; + this.admins = admins; + this.banned = banned; + } + + public String getName() { + return name; + } + + public ArrayList getAdmins() { + return admins; + } + + public ArrayList getBanned() { + return banned; + } + + public void setName(String name) { + this.name = name; + } + + public void setAdmins(ArrayList admins) { + this.admins = admins; + } + + public void setBanned(ArrayList banned) { + this.banned = banned; + } +} diff --git a/app/src/main/java/io/github/chronosx88/influence/models/JoinChatMessage.java b/app/src/main/java/io/github/chronosx88/influence/models/JoinChatMessage.java new file mode 100644 index 0000000..e12162a --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/models/JoinChatMessage.java @@ -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; + } +} diff --git a/app/src/main/java/io/github/chronosx88/influence/models/NewChatRequestMessage.java b/app/src/main/java/io/github/chronosx88/influence/models/NewChatRequestMessage.java index 4440fd2..f1e17e4 100644 --- a/app/src/main/java/io/github/chronosx88/influence/models/NewChatRequestMessage.java +++ b/app/src/main/java/io/github/chronosx88/influence/models/NewChatRequestMessage.java @@ -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; + } } diff --git a/app/src/main/java/io/github/chronosx88/influence/models/NextChunkReference.java b/app/src/main/java/io/github/chronosx88/influence/models/NextChunkReference.java new file mode 100644 index 0000000..7251de8 --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/models/NextChunkReference.java @@ -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; + } +} diff --git a/app/src/main/java/io/github/chronosx88/influence/models/SendMessage.java b/app/src/main/java/io/github/chronosx88/influence/models/SendMessage.java deleted file mode 100644 index 388402d..0000000 --- a/app/src/main/java/io/github/chronosx88/influence/models/SendMessage.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/io/github/chronosx88/influence/models/SuccessfullySentMessage.java b/app/src/main/java/io/github/chronosx88/influence/models/SuccessfullySentMessage.java deleted file mode 100644 index da24345..0000000 --- a/app/src/main/java/io/github/chronosx88/influence/models/SuccessfullySentMessage.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/io/github/chronosx88/influence/models/TextMessage.java b/app/src/main/java/io/github/chronosx88/influence/models/TextMessage.java new file mode 100644 index 0000000..91c7276 --- /dev/null +++ b/app/src/main/java/io/github/chronosx88/influence/models/TextMessage.java @@ -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; + } +} diff --git a/app/src/main/java/io/github/chronosx88/influence/models/daos/ChatDao.java b/app/src/main/java/io/github/chronosx88/influence/models/daos/ChatDao.java index 32e8f0a..c9a4f8c 100644 --- a/app/src/main/java/io/github/chronosx88/influence/models/daos/ChatDao.java +++ b/app/src/main/java/io/github/chronosx88/influence/models/daos/ChatDao.java @@ -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 getChatByChatID(String chatID); - @Query("SELECT * FROM chats WHERE id = :id") - List getChatByID(String id); + @Update + void updateChat(ChatEntity chat); } diff --git a/app/src/main/java/io/github/chronosx88/influence/models/daos/MessageDao.java b/app/src/main/java/io/github/chronosx88/influence/models/daos/MessageDao.java index 5bbb1ca..85afa75 100644 --- a/app/src/main/java/io/github/chronosx88/influence/models/daos/MessageDao.java +++ b/app/src/main/java/io/github/chronosx88/influence/models/daos/MessageDao.java @@ -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 getMessagesByChatID(String chatID); - @Query("SELECT * FROM messages WHERE id = :id") - List getMessageByID(long id); + @Query("SELECT * FROM messages WHERE messageID = :messageID") + List 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); } diff --git a/app/src/main/java/io/github/chronosx88/influence/models/roomEntities/ChatEntity.java b/app/src/main/java/io/github/chronosx88/influence/models/roomEntities/ChatEntity.java index f6ffd09..0315c08 100644 --- a/app/src/main/java/io/github/chronosx88/influence/models/roomEntities/ChatEntity.java +++ b/app/src/main/java/io/github/chronosx88/influence/models/roomEntities/ChatEntity.java @@ -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 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 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; } } diff --git a/app/src/main/java/io/github/chronosx88/influence/models/roomEntities/MessageEntity.java b/app/src/main/java/io/github/chronosx88/influence/models/roomEntities/MessageEntity.java index 479b4cb..bdd8eaa 100644 --- a/app/src/main/java/io/github/chronosx88/influence/models/roomEntities/MessageEntity.java +++ b/app/src/main/java/io/github/chronosx88/influence/models/roomEntities/MessageEntity.java @@ -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; } } diff --git a/app/src/main/java/io/github/chronosx88/influence/presenters/ChatPresenter.java b/app/src/main/java/io/github/chronosx88/influence/presenters/ChatPresenter.java index 2b6d910..1728ea4 100644 --- a/app/src/main/java/io/github/chronosx88/influence/presenters/ChatPresenter.java +++ b/app/src/main/java/io/github/chronosx88/influence/presenters/ChatPresenter.java @@ -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); } }