[WIP] Changed architecture: now communications is indirect

This commit is contained in:
ChronosX88 2019-04-01 21:52:15 +04:00
parent 627547c648
commit 94643207b7
32 changed files with 907 additions and 384 deletions

View File

@ -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'

View File

@ -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\")"
]
}
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -1,5 +0,0 @@
package io.github.chronosx88.influence.helpers;
public class MessageTypes {
public static final int USUAL_MESSAGE = 0x0;
}

View File

@ -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())));
}
}
}

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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();

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}