[WIP] Started implement creating chat system (and handling system)

This commit is contained in:
ChronosX88 2019-03-21 14:59:17 +04:00
parent a8bc9ab2fc
commit a65b871eab
Signed by: ChronosXYZ
GPG Key ID: 085A69A82C8C511A
16 changed files with 337 additions and 29 deletions

View File

@ -0,0 +1,5 @@
package io.github.chronosx88.influence.contracts.startchat;
public interface StartChatLogicContract {
void sendStartChatMessage(String peerID);
}

View File

@ -0,0 +1,5 @@
package io.github.chronosx88.influence.contracts.startchat;
public interface StartChatPresenterContract {
void startChatWithPeer(String peerID);
}

View File

@ -0,0 +1,6 @@
package io.github.chronosx88.influence.contracts.startchat;
public interface StartChatViewContract {
void showMessage(String message);
void showProgressDialog(boolean enabled);
}

View File

@ -0,0 +1,60 @@
package io.github.chronosx88.influence.helpers;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import net.tomp2p.dht.PeerDHT;
import net.tomp2p.peers.PeerAddress;
import io.github.chronosx88.influence.contracts.observer.Observer;
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.observable.MainObservable;
public class NetworkHandler implements Observer {
private Gson gson;
private PeerDHT peerDHT;
public NetworkHandler() {
gson = new Gson();
peerDHT = AppHelper.getPeerDHT();
AppHelper.getObservable().register(this, MainObservable.OTHER_ACTIONS_CHANNEL);
}
@Override
public void handleEvent(JsonObject object) {
new Thread(() -> {
switch (object.get("action").getAsInt()) {
case NetworkActions.START_CHAT: {
String chatStarterPlainAddress = object.get("senderAddress").getAsString();
createChatEntry(object.get("chatID").getAsString(), chatStarterPlainAddress);
handleIncomingChat(object.get("chatID").getAsString(), PrepareData.prepareFromStore(chatStarterPlainAddress));
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.NEW_CHAT);
AppHelper.getObservable().notifyObservers(jsonObject, MainObservable.UI_ACTIONS_CHANNEL);
break;
}
case NetworkActions.SUCCESSFULL_CREATE_CHAT: {
createChatEntry(object.get("chatID").getAsString(), object.get("senderAddress").getAsString());
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.SUCCESSFULL_CREATE_CHAT);
AppHelper.getObservable().notifyObservers(jsonObject, MainObservable.UI_ACTIONS_CHANNEL);
break;
}
}
}).start();
}
private void createChatEntry(String chatID, String peerAddress) {
// TODO: make saving chat in db
}
private void handleIncomingChat(String chatID, PeerAddress chatStarterAddress) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", NetworkActions.SUCCESSFULL_CREATE_CHAT);
jsonObject.addProperty("chatID", chatID);
jsonObject.addProperty("senderAddress", PrepareData.prepareToStore(peerDHT.peerAddress()));
AppHelper.getPeerDHT().peer().sendDirect(chatStarterAddress).object(gson.toJson(jsonObject)).start().awaitUninterruptibly();
}
}

View File

@ -0,0 +1,17 @@
package io.github.chronosx88.influence.helpers;
import android.util.Base64;
import java.nio.charset.StandardCharsets;
public class PrepareData {
public static <T> String prepareToStore(T object) {
String serializedObject = Serializer.serializeObject(object);
return Base64.encodeToString(serializedObject.getBytes(StandardCharsets.UTF_8), Base64.URL_SAFE);
}
public static <T> T prepareFromStore(String object) {
String decodedString = new String(Base64.decode(object, Base64.URL_SAFE), StandardCharsets.UTF_8);
return Serializer.deserializeObject(decodedString);
}
}

View File

@ -1,4 +1,4 @@
package io.github.chronosx88.influence.helpers;
package io.github.chronosx88.influence.helpers.actions;
public class NetworkActions {
public static final int START_CHAT = 0x0;
@ -6,4 +6,5 @@ public class NetworkActions {
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;
}

View File

@ -1,4 +1,4 @@
package io.github.chronosx88.influence.helpers;
package io.github.chronosx88.influence.helpers.actions;
public class UIActions {
public static final int BOOTSTRAP_NOT_SPECIFIED = 0x0;
@ -8,4 +8,6 @@ public class UIActions {
public static final int RELAY_CONNECTION_ERROR = 0x4;
public static final int BOOTSTRAP_ERROR = 0x5;
public static final int NEW_CHAT = 0x6;
public static final int PEER_NOT_EXIST = 0x7;
public static final int SUCCESSFULL_CREATE_CHAT = 0x8;
}

View File

@ -7,8 +7,8 @@ import java.util.List;
import io.github.chronosx88.influence.contracts.chatlist.ChatListLogicContract;
import io.github.chronosx88.influence.contracts.observer.Observer;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.NetworkActions;
import io.github.chronosx88.influence.helpers.UIActions;
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
import io.github.chronosx88.influence.observable.MainObservable;

View File

@ -2,6 +2,7 @@ package io.github.chronosx88.influence.logic;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Base64;
import android.util.Log;
import com.google.gson.Gson;
@ -19,20 +20,30 @@ import net.tomp2p.p2p.PeerBuilder;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.relay.tcp.TCPRelayClientConfig;
import net.tomp2p.replication.AutoReplication;
import net.tomp2p.storage.Data;
import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
import io.github.chronosx88.influence.contracts.main.MainLogicContract;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.NetworkActions;
import io.github.chronosx88.influence.helpers.Serializer;
import io.github.chronosx88.influence.helpers.StorageMVStore;
import io.github.chronosx88.influence.helpers.UIActions;
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.observable.MainObservable;
public class MainLogic implements MainLogicContract {
@ -45,6 +56,7 @@ public class MainLogic implements MainLogicContract {
private InetAddress bootstrapAddress = null;
private PeerAddress bootstrapPeerAddress = null;
private Gson gson;
private AutoReplication replication;
public MainLogic() {
this.context = AppHelper.getContext();
@ -120,6 +132,11 @@ public class MainLogic implements MainLogicContract {
AppHelper.storePeerID(preferences.getString("peerID", null));
AppHelper.storePeerDHT(peerDHT);
setReceiveHandler();
Gson gson = new Gson();
JsonObject publicProfile = new JsonObject();
publicProfile.addProperty("peerAddress", Base64.encodeToString(Serializer.serializeObject(peerDHT.peerAddress()).getBytes(StandardCharsets.UTF_8), Base64.URL_SAFE));
peerDHT.put(Number160.createHash(preferences.getString("peerID", null) + "_profile")).data(new Data(gson.toJson(publicProfile)).protectEntry(createMainKeyPair())).start().awaitUninterruptibly();
replication = new AutoReplication(peerDHT.peer()).start();
} catch (IOException e) {
e.printStackTrace();
}
@ -184,7 +201,8 @@ public class MainLogic implements MainLogicContract {
@Override
public void shutdownPeer() {
peerDHT.shutdown();
replication.shutdown().start();
peerDHT.peer().announceShutdown().start().awaitUninterruptibly();
}
private boolean checkFirstRun() {
@ -196,4 +214,34 @@ public class MainLogic implements MainLogicContract {
}
return false;
}
private KeyPair createMainKeyPair() {
KeyPair kp = null;
try {
File keyPairDir = new File(AppHelper.getContext().getFilesDir().getAbsoluteFile(), "keyPairs");
if (!keyPairDir.exists())
keyPairDir.mkdir();
File mainKeyPairFile = new File(keyPairDir, "mainKeyPair.kp");
if (!mainKeyPairFile.exists()) {
mainKeyPairFile.createNewFile();
try {
kp = KeyPairGenerator.getInstance("RSA").generateKeyPair();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
FileOutputStream outputStream = new FileOutputStream(mainKeyPairFile);
outputStream.write(Serializer.serializeObject(kp).getBytes(StandardCharsets.UTF_8));
outputStream.close();
return kp;
}
FileInputStream inputStream = new FileInputStream(mainKeyPairFile);
byte[] serializedKeyPair = new byte[(int) mainKeyPairFile.length()];
inputStream.read(serializedKeyPair);
inputStream.close();
kp = Serializer.deserializeObject(new String(serializedKeyPair, StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
return kp;
}
}

View File

@ -0,0 +1,79 @@
package io.github.chronosx88.influence.logic;
import android.util.Base64;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.tomp2p.dht.FutureGet;
import net.tomp2p.dht.PeerDHT;
import net.tomp2p.futures.FuturePing;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import io.github.chronosx88.influence.contracts.startchat.StartChatLogicContract;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.PrepareData;
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.observable.MainObservable;
public class StartChatLogic implements StartChatLogicContract {
private PeerDHT peerDHT;
private Gson gson;
public StartChatLogic() {
peerDHT = AppHelper.getPeerDHT();
gson = new Gson();
}
@Override
public void sendStartChatMessage(String peerID) {
new Thread(() -> {
JsonObject recipientPublicProfile = getPublicProfile(peerID);
if(recipientPublicProfile == null) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.PEER_NOT_EXIST);
AppHelper.getObservable().notifyObservers(jsonObject, MainObservable.UI_ACTIONS_CHANNEL);
return;
}
String peerAddressString = recipientPublicProfile.get("peerAddress").getAsString();
PeerAddress peerAddress = Serializer.deserializeObject(new String(Base64.decode(peerAddressString, Base64.URL_SAFE), StandardCharsets.UTF_8));
FuturePing ping = peerDHT.peer().ping().peerAddress(peerAddress).start().awaitUninterruptibly();
if(ping.isSuccess()) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", NetworkActions.START_CHAT);
jsonObject.addProperty("chatID", UUID.randomUUID().toString());
// TODO: Append public key to new chat request (for encryption)
jsonObject.addProperty("senderAddress", PrepareData.prepareToStore(peerDHT.peerAddress()));
peerDHT.peer().sendDirect(peerAddress).object(gson.toJson(jsonObject)).start();
} else {
// TODO: put chat entry to "*peerID*_newChats". That peer later will fetch this new chats
}
}).start();
}
private JsonObject getPublicProfile(String peerID) {
JsonObject publicProfile;
FutureGet futureGetProfile = peerDHT.get(Number160.createHash(peerID + "_profile")).start().awaitUninterruptibly();
if (futureGetProfile.isSuccess()) {
String jsonString = null;
try {
jsonString = (String) futureGetProfile.data().object();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
publicProfile = new JsonParser().parse(jsonString).getAsJsonObject();
return publicProfile;
}
return null;
}
}

View File

@ -9,11 +9,13 @@ import androidx.room.PrimaryKey;
public class ChatEntity {
@NonNull @PrimaryKey String id;
@ColumnInfo String name;
@ColumnInfo String peerAddresses;
@ColumnInfo String keyPairID;
public ChatEntity(String id, String name, String keyPairID) {
public ChatEntity(String id, String name, String peerAddresses, String keyPairID) {
this.id = id;
this.name = name;
this.peerAddresses = peerAddresses;
this.keyPairID = keyPairID;
}
@ -25,6 +27,8 @@ public class ChatEntity {
return keyPairID;
}
public String getPeerAddress() { return peerAddresses; }
public String getName() {
return name;
}

View File

@ -0,0 +1,45 @@
package io.github.chronosx88.influence.presenters;
import com.google.gson.JsonObject;
import io.github.chronosx88.influence.contracts.observer.Observer;
import io.github.chronosx88.influence.contracts.startchat.StartChatLogicContract;
import io.github.chronosx88.influence.contracts.startchat.StartChatPresenterContract;
import io.github.chronosx88.influence.contracts.startchat.StartChatViewContract;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.logic.StartChatLogic;
import io.github.chronosx88.influence.observable.MainObservable;
public class StartChatPresenter implements StartChatPresenterContract, Observer {
private StartChatViewContract view;
private StartChatLogicContract logic;
public StartChatPresenter(StartChatViewContract view) {
this.view = view;
this.logic = new StartChatLogic();
AppHelper.getObservable().register(this, MainObservable.UI_ACTIONS_CHANNEL);
}
@Override
public void startChatWithPeer(String peerID) {
// TODO
}
@Override
public void handleEvent(JsonObject object) {
switch (object.get("action").getAsInt()) {
case UIActions.PEER_NOT_EXIST: {
view.showProgressDialog(false);
view.showMessage("Данный узел не существует!");
break;
}
case UIActions.SUCCESSFULL_CREATE_CHAT: {
view.showProgressDialog(false);
view.showMessage("Чат успешно создан");
break;
}
}
}
}

View File

@ -17,7 +17,7 @@ import io.github.chronosx88.influence.contracts.main.MainPresenterContract;
import io.github.chronosx88.influence.contracts.main.MainViewContract;
import io.github.chronosx88.influence.contracts.observer.Observer;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.UIActions;
import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.observable.MainObservable;
import io.github.chronosx88.influence.presenters.MainPresenter;
import io.github.chronosx88.influence.views.fragments.ChatListFragment;

View File

@ -17,7 +17,7 @@ import io.github.chronosx88.influence.contracts.chatlist.ChatListViewContract;
import io.github.chronosx88.influence.contracts.observer.Observer;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.ChatListAdapter;
import io.github.chronosx88.influence.helpers.UIActions;
import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.observable.MainObservable;
import io.github.chronosx88.influence.presenters.ChatListPresenter;

View File

@ -1,20 +1,23 @@
package io.github.chronosx88.influence.views.fragments;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.contracts.startchat.StartChatViewContract;
public class StartChatFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public class StartChatFragment extends Fragment implements StartChatViewContract {
private TextInputLayout textInputPeerID;
private ProgressDialog progressDialog;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
@ -25,6 +28,25 @@ public class StartChatFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// TODO
textInputPeerID = view.findViewById(R.id.textInputPeerID);
progressDialog = new ProgressDialog(getActivity(), R.style.AlertDialogTheme);
progressDialog.setCancelable(false);
progressDialog.setProgressStyle(android.R.style.Widget_ProgressBar_Small);
}
@Override
public void showMessage(String message) {
Snackbar.make(getView().findViewById(R.id.start_chat_coordinator), message, Snackbar.LENGTH_SHORT).show();
}
@Override
public void showProgressDialog(boolean enabled) {
if(enabled) {
progressDialog.show();
} else {
progressDialog.dismiss();
}
}
// TODO: clear text input
}

View File

@ -1,18 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:gravity="center"
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/start_chat_coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:layout_width="346dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:hint="Peer ID"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Создать чат"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputPeerID"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="346dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:hint="Peer ID"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Создать чат"/>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>