[WIP] Migrating to XMPP, added XMPPConnectionService, LoginActivity

This commit is contained in:
ChronosX88 2019-05-19 15:29:02 +04:00
parent ff8aa48b3e
commit 1f0416fda4
36 changed files with 634 additions and 1018 deletions

View File

@ -42,6 +42,7 @@ dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.appcompat:appcompat:1.1.0-alpha02'
implementation "androidx.room:room-runtime:2.1.0-alpha04"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
annotationProcessor "androidx.room:room-compiler:2.1.0-alpha04"
implementation 'org.slf4j:slf4j-log4j12:1.7.26'
implementation('net.tomp2p:tomp2p-all:5.0-Beta8') {
@ -58,6 +59,11 @@ dependencies {
implementation 'com.esotericsoftware:kryo:5.0.0-RC1'
implementation 'com.github.instacart.truetime-android:library:3.4'
implementation 'org.igniterealtime.smack:smack-core:4.3.3'
implementation 'org.igniterealtime.smack:smack-tcp:4.3.3'
implementation 'org.igniterealtime.smack:smack-android:4.3.3'
implementation 'org.igniterealtime.smack:smack-extensions:4.3.3'
}
repositories {
mavenCentral()

View File

@ -13,6 +13,13 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".views.LoginActivity"></activity>
<service
android:name=".XMPPConnectionService"
android:enabled="true"
android:exported="true" />
<activity android:name=".views.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2019 ChronosX88
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.github.chronosx88.influence;
public class LoginCredentials {
String username = "";
String password = "";
String jabberHost = "";
}

View File

@ -0,0 +1,191 @@
/*
* Copyright (C) 2019 ChronosX88
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.github.chronosx88.influence;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.preference.PreferenceManager;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.ReconnectionManager;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.chat2.Chat;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import java.io.IOException;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.NetworkHandler;
public class XMPPConnection implements ConnectionListener {
private final static String LOG_TAG = "XMPPConnection";
private LoginCredentials credentials = new LoginCredentials();
private XMPPTCPConnection connection = null;
private SharedPreferences prefs;
private NetworkHandler networkHandler;
private BroadcastReceiver sendMessageReceiver = null;
private Context context;
public enum ConnectionState {
CONNECTED,
AUTHENTICATED,
CONNECTING,
DISCONNECTING,
DISCONNECTED
}
public enum SessionState {
LOGGED_IN,
LOGGED_OUT
}
public XMPPConnection(Context context) {
String jid = prefs.getString("jid", null);
String password = prefs.getString("pass", null);
if(jid != null && password != null) {
String username = jid.split("@")[0];
String jabberHost = jid.split("@")[1];
credentials.username = username;
credentials.jabberHost = jabberHost;
credentials.password = password;
}
networkHandler = new NetworkHandler(context);
prefs = PreferenceManager.getDefaultSharedPreferences(context);
this.context = context;
}
public void connect() throws XMPPException, SmackException, IOException {
if(connection == null) {
XMPPTCPConnectionConfiguration conf = XMPPTCPConnectionConfiguration.builder()
.setXmppDomain(credentials.jabberHost)
.setHost(credentials.jabberHost)
.setResource(AppHelper.APP_NAME)
.setKeystoreType(null)
.setSecurityMode(ConnectionConfiguration.SecurityMode.required)
.setCompressionEnabled(true)
.build();
setupSendMessageReceiver();
connection = new XMPPTCPConnection(conf);
connection.addConnectionListener(this);
try {
connection.connect();
connection.login(credentials.username, credentials.password);
} catch (InterruptedException e) {
e.printStackTrace();
}
ChatManager.getInstanceFor(connection).addIncomingListener(networkHandler);
ReconnectionManager reconnectionManager = ReconnectionManager.getInstanceFor(connection);
ReconnectionManager.setEnabledPerDefault(true);
reconnectionManager.enableAutomaticReconnection();
}
}
public void disconnect() {
prefs.edit().putBoolean("logged_in", false).apply();
if(connection != null) {
connection.disconnect();
connection = null;
}
}
@Override
public void connected(org.jivesoftware.smack.XMPPConnection connection) {
XMPPConnectionService.connectionState = ConnectionState.CONNECTED;
}
@Override
public void authenticated(org.jivesoftware.smack.XMPPConnection connection, boolean resumed) {
XMPPConnectionService.sessionState = SessionState.LOGGED_IN;
prefs.edit().putBoolean("logged_in", true).apply();
}
@Override
public void connectionClosed() {
XMPPConnectionService.connectionState = ConnectionState.DISCONNECTED;
XMPPConnectionService.sessionState = SessionState.LOGGED_OUT;
prefs.edit().putBoolean("logged_in", false).apply();
}
@Override
public void connectionClosedOnError(Exception e) {
XMPPConnectionService.connectionState = ConnectionState.DISCONNECTED;
XMPPConnectionService.sessionState = SessionState.LOGGED_OUT;
prefs.edit().putBoolean("logged_in", false).apply();
Log.e(LOG_TAG, "Connection closed, exception occurred");
e.printStackTrace();
}
private void setupSendMessageReceiver() {
sendMessageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case XMPPConnectionService.INTENT_SEND_MESSAGE: {
String recipientJid = intent.getStringExtra(XMPPConnectionService.MESSAGE_RECIPIENT);
String messageText = intent.getStringExtra(XMPPConnectionService.MESSAGE_BODY);
sendMessage(recipientJid, messageText);
break;
}
}
}
};
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(XMPPConnectionService.INTENT_SEND_MESSAGE);
context.registerReceiver(sendMessageReceiver, intentFilter);
}
private void sendMessage(String recipientJid, String messageText) {
EntityBareJid jid = null;
try {
jid = JidCreate.entityBareFrom(recipientJid);
} catch (XmppStringprepException e) {
e.printStackTrace();
}
Chat chat = ChatManager.getInstanceFor(connection).chatWith(jid);
try {
Message message = new Message(jid, Message.Type.chat);
message.setBody(messageText);
chat.send(message);
} catch (SmackException.NotConnectedException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public XMPPTCPConnection getConnection() {
return connection;
}
}

View File

@ -0,0 +1,110 @@
/*
* Copyright (C) 2019 ChronosX88
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.github.chronosx88.influence;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import java.io.IOException;
public class XMPPConnectionService extends Service {
public static final String INTENT_NEW_MESSAGE = "io.github.chronosx88.intents.new_message";
public static final String INTENT_SEND_MESSAGE = "io.github.chronosx88.intents.send_message";
public static final String INTENT_AUTHENTICATED = "io.github.chronosx88.intents.authenticated";
public static final String INTENT_AUTHENTICATION_FAILED = "io.github.chronosx88.intents.authentication_failed";
public static final String MESSAGE_CHATID = "chat_jid";
public static final String MESSAGE_ID = "message_id";
public static final String MESSAGE_BODY = "message_body";
public static final String MESSAGE_RECIPIENT = "message_recipient";
public static XMPPConnection.ConnectionState connectionState = XMPPConnection.ConnectionState.DISCONNECTED;
public static XMPPConnection.SessionState sessionState = XMPPConnection.SessionState.LOGGED_OUT;
private Thread thread;
private Handler threadHandler;
private boolean isThreadAlive = false;
private XMPPConnection connection;
private Context context;
public XMPPConnectionService(Context context) {
this.context = context;
}
@Override
public IBinder onBind(Intent intent) { return null; }
public void onServiceStart() {
if(!isThreadAlive)
{
isThreadAlive = true;
if(thread == null || !thread.isAlive()) {
thread = new Thread(() -> {
Looper.prepare();
threadHandler = new Handler();
createConnection();
Looper.loop();
});
thread.start();
}
}
}
private void onServiceStop() {
isThreadAlive = false;
threadHandler.post(() -> {
if(connection != null) {
connection.disconnect();
}
});
}
private void createConnection() {
if(connection == null) {
connection = new XMPPConnection(this);
}
try {
connection.connect();
} catch (IOException | SmackException | XMPPException e) {
Intent intent = new Intent(INTENT_AUTHENTICATION_FAILED);
e.printStackTrace();
//Stop the service all together.
stopSelf();
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
onServiceStart();
return Service.START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
onServiceStop();
}
}

View File

@ -9,6 +9,10 @@ import io.github.chronosx88.influence.models.roomEntities.MessageEntity
interface CoreContracts {
interface ViewWithLoadingScreen {
fun loadingScreen(state: Boolean);
}
// -----ChatList-----
interface IChatListLogicContract {
@ -27,21 +31,6 @@ interface CoreContracts {
fun updateChatList(adapter: ChatListAdapter, chats: List<ChatEntity>)
}
// -----StartChat-----
interface IStartChatLogicContract {
fun sendStartChatMessage(peerID: String)
}
interface IStartChatPresenterContract {
fun startChatWithPeer(peerID: String)
}
interface IStartChatViewContract {
fun showMessage(message: String)
fun showProgressDialog(enabled: Boolean)
}
// -----MainActivity-----
interface IMainLogicContract {
@ -94,4 +83,7 @@ interface CoreContracts {
fun showMessage(message: String)
fun refreshScreen()
}
// -----LoginActivity-----
interface ILoginViewContract : ViewWithLoadingScreen
}

View File

@ -3,6 +3,7 @@ package io.github.chronosx88.influence.helpers;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import net.tomp2p.dht.PeerDHT;
@ -22,11 +23,10 @@ import io.github.chronosx88.influence.observable.MainObservable;
public class AppHelper extends MultiDexApplication {
private static Application instance;
private static MainObservable observable;
private static String peerID;
private static PeerDHT peerDHT;
public final static String APP_NAME = "Influence";
private static String jid;
private static RoomHelper chatDB;
private static NetworkHandler networkHandler;
private static String username = "";
private static SharedPreferences preferences;
@Override
@ -37,7 +37,7 @@ public class AppHelper extends MultiDexApplication {
chatDB = Room.databaseBuilder(getApplicationContext(), RoomHelper.class, "chatDB")
.allowMainThreadQueries()
.build();
preferences = getApplicationContext().getSharedPreferences("io.github.chronosx88.influence_preferences", MODE_PRIVATE);
preferences = PreferenceManager.getDefaultSharedPreferences(instance);
new Thread(() -> {
try {
TrueTime.build().initialize();
@ -47,28 +47,18 @@ public class AppHelper extends MultiDexApplication {
}).start();
}
public static void storePeerID(String peerID1) { peerID = peerID1; }
public static void updateUsername(String username1) { username = username1; }
public static void storePeerDHT(PeerDHT peerDHT1) { peerDHT = peerDHT1; }
public static Context getContext() {
return instance.getApplicationContext();
}
public static MainObservable getObservable() { return observable; }
public static String getPeerID() { return peerID; }
public static String getJid() { return jid; }
public static String getUsername() { return username; }
public static PeerDHT getPeerDHT() { return peerDHT; }
public static void setJid(String jid1) { jid = jid1; }
public static RoomHelper getChatDB() { return chatDB; }
public static void initNetworkHandler() { networkHandler = new NetworkHandler(); }
public static SharedPreferences getPreferences() {
return preferences;
}

View File

@ -1,98 +0,0 @@
package io.github.chronosx88.influence.helpers
import android.util.Log
import com.sleepycat.bind.EntryBinding
import com.sleepycat.je.DatabaseEntry
import io.netty.buffer.Unpooled
import net.tomp2p.connection.SignatureFactory
import net.tomp2p.storage.Data
import java.io.*
import java.nio.ByteBuffer
import java.security.InvalidKeyException
import java.security.SignatureException
class DataSerializer(private val signatureFactory: SignatureFactory) : EntryBinding<Data>, Serializable {
private val LOG_TAG = "DataSerializer"
override fun entryToObject(databaseEntry: DatabaseEntry): Data? {
if (databaseEntry.data == null) {
return null
}
val dataInput = ByteArrayInputStream(databaseEntry.data)
var buf = Unpooled.buffer()
var data: Data? = null
while (data == null) {
buf.writeByte(dataInput.read())
data = Data.decodeHeader(buf, signatureFactory)
}
val len = data.length()
var me = ByteArray(len)
try {
dataInput.read(me)
} catch (e: IOException) {
e.printStackTrace()
}
buf = Unpooled.wrappedBuffer(me)
var retVal = data.decodeBuffer(buf)
if (!retVal) {
Log.e(LOG_TAG, "# ERROR: Data could not be deserialized!")
}
if (data.isSigned) {
me = ByteArray(signatureFactory.signatureSize())
dataInput.read(me)
buf = Unpooled.wrappedBuffer(me)
}
retVal = data.decodeDone(buf, signatureFactory);
if(!retVal) {
throw IOException("Signature could not be read!")
}
return data
}
override fun objectToEntry(data: Data, databaseEntry: DatabaseEntry) {
val out = ByteArrayOutputStream()
val acb = Unpooled.buffer()
// store data to disk
// header first
data.encodeHeader(acb, signatureFactory)
writeData(out, acb.nioBuffers())
acb.skipBytes(acb.writerIndex())
// next data - no need to copy to another buffer, just take the data
// from memory
writeData(out, data.toByteBuffers())
// rest
try {
data.encodeDone(acb, signatureFactory)
writeData(out, acb.nioBuffers())
} catch (e: InvalidKeyException) {
throw IOException(e)
} catch (e: SignatureException) {
throw IOException(e)
}
out.flush()
databaseEntry.data = out.toByteArray()
out.close()
}
@Throws(IOException::class)
private fun writeData(out: OutputStream, nioBuffers: Array<ByteBuffer>) {
val length = nioBuffers.size
for (i in 0 until length) {
val remaining = nioBuffers[i].remaining()
if (nioBuffers[i].hasArray()) {
out.write(nioBuffers[i].array(), nioBuffers[i].arrayOffset(), remaining)
} else {
val me = ByteArray(remaining)
nioBuffers[i].get(me)
out.write(me)
}
}
}
companion object {
private const val serialVersionUID = 1428836065493792295L
}
}

View File

@ -1,23 +0,0 @@
package io.github.chronosx88.influence.helpers;
import android.util.Log;
import net.tomp2p.dht.Storage;
public class JVMShutdownHook extends Thread {
Storage storage;
public JVMShutdownHook(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
super.run();
Log.d("JVMShutdownHook", "# Closing storage...");
storage.close();
Log.d("JVMShutdownHook", "# Storage is closed");
}
}

View File

@ -12,39 +12,22 @@ public class LocalDBWrapper {
private static final String LOG_TAG = "LocalDBWrapper";
private static RoomHelper dbInstance = AppHelper.getChatDB();
/**
* Create a chat entry in the local database.
* @param chatID Chat ID
* @param name Chat name
* @param metadataRef Reference to general chat metadata (key in DHT)
* @param membersRef Reference to member list
*/
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));
public static void createChatEntry(String jid, String chatName) {
dbInstance.chatDao().addChat(new ChatEntity(jid, chatName));
}
/**
* 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 username Sender username
* @param senderID Sender peer ID
* @param timestamp Message timestamp
* @param text Message text
* @return New message
*/
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);
public static long createMessageEntry(String jid, String senderJid, long timestamp, String text, boolean isSent, boolean isRead) {
List<ChatEntity> chatEntities = AppHelper.getChatDB().chatDao().getChatByChatID(jid);
if(chatEntities.size() < 1) {
Log.e(LOG_TAG, "Failed to create message entry because chat " + chatID + " doesn't exists!");
return null;
Log.e(LOG_TAG, "Failed to create message entry because chat " + jid + " doesn't exists!");
return -1;
}
MessageEntity message = new MessageEntity(type, messageID, chatID, senderID, username, timestamp, text, isSent, isRead);
dbInstance.messageDao().insertMessage(message);
return message;
MessageEntity message = new MessageEntity(jid, senderJid, timestamp, text, isSent, isRead);
long index = dbInstance.messageDao().insertMessage(message);
return index;
}
public static MessageEntity getMessageByID(String messageID) {
public static MessageEntity getMessageByID(long messageID) {
List<MessageEntity> messages = dbInstance.messageDao().getMessageByID(messageID);
if(messages.isEmpty()) {
return null;

View File

@ -1,78 +1,35 @@
package io.github.chronosx88.influence.helpers;
import com.google.gson.Gson;
import android.content.Context;
import android.content.Intent;
import net.tomp2p.dht.PeerDHT;
import net.tomp2p.peers.Number640;
import net.tomp2p.storage.Data;
import com.instacart.library.truetime.TrueTime;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import org.jivesoftware.smack.chat2.Chat;
import org.jivesoftware.smack.chat2.IncomingChatMessageListener;
import org.jivesoftware.smack.packet.Message;
import org.jxmpp.jid.EntityBareJid;
import io.github.chronosx88.influence.contracts.observer.INetworkObserver;
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.XMPPConnectionService;
public class NetworkHandler implements INetworkObserver {
public class NetworkHandler implements IncomingChatMessageListener {
private final static String LOG_TAG = "NetworkHandler";
private static Gson gson = new Gson();
private static PeerDHT peerDHT = AppHelper.getPeerDHT();
private static KeyPairManager keyPairManager = new KeyPairManager();
private Context context;
public NetworkHandler() {
AppHelper.getObservable().register(this);
public NetworkHandler(Context context) {
this.context = context;
}
@Override
public void handleEvent(Object object) {
// Empty
}
public static void handlePendingChatRequests() {
Map<Number640, Data> pendingChats = P2PUtils.get(AppHelper.getPeerID() + "_pendingChats");
if (pendingChats != null) {
for (Map.Entry<Number640, Data> entry : pendingChats.entrySet()) {
NewChatRequestMessage newChatRequestMessage = null;
try {
newChatRequestMessage = gson.fromJson((String) entry.getValue().object(), NewChatRequestMessage.class);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
ChatMember chatMember = new ChatMember(AppHelper.getPeerID(), AppHelper.getPeerID());
Data putData = null;
try {
putData = new Data(gson.toJson(chatMember)).protectEntry(keyPairManager.openMainKeyPair());
} catch (IOException e) {
e.printStackTrace();
}
P2PUtils.put(newChatRequestMessage.getChatID() + "_members", AppHelper.getPeerID(), putData);
LocalDBWrapper.createChatEntry(
newChatRequestMessage.getChatID(),
newChatRequestMessage.getUsername(),
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.getUsername() == null ? AppHelper.getPeerID() : AppHelper.getUsername(), newChatRequestMessage.getChatID(), System.currentTimeMillis()))).protectEntry(keyPairManager.openMainKeyPair()));
} catch (IOException e) {
e.printStackTrace();
}
ObservableUtils.notifyUI(UIActions.SUCCESSFUL_CREATE_CHAT);
}
public void newIncomingMessage(EntityBareJid from, Message message, Chat chat) {
if(LocalDBWrapper.getChatByChatID(from.asEntityBareJidString()) == null) {
LocalDBWrapper.createChatEntry(chat.getXmppAddressOfChatPartner().asUnescapedString(), chat.getXmppAddressOfChatPartner().asBareJid().asUnescapedString());
}
long messageID = LocalDBWrapper.createMessageEntry(chat.getXmppAddressOfChatPartner().asUnescapedString(), from.asUnescapedString(), TrueTime.now().getTime(), message.getBody(), true, false);
Intent intent = new Intent(XMPPConnectionService.INTENT_NEW_MESSAGE);
intent.setPackage(context.getPackageName());
intent.putExtra(XMPPConnectionService.MESSAGE_CHATID, chat.getXmppAddressOfChatPartner().toString());
intent.putExtra(XMPPConnectionService.MESSAGE_ID, messageID);
context.sendBroadcast(intent);
}
}

View File

@ -1,74 +0,0 @@
package io.github.chronosx88.influence.helpers;
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;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.Number640;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.storage.Data;
import java.security.KeyPair;
import java.util.Map;
public class P2PUtils {
private static Gson gson = new Gson();
private static PeerDHT peerDHT = AppHelper.getPeerDHT();
public static boolean put(String locationKey, String contentKey, Data data) {
FuturePut futurePut = peerDHT
.put(Number160.createHash(locationKey))
.data(contentKey == null ? Number160.ZERO : Number160.createHash(contentKey), data)
.start()
.awaitUninterruptibly();
return futurePut.isSuccess();
}
public static boolean put(String locationKey, String contentKey, Data data, KeyPair keyPair) {
FuturePut futurePut = peerDHT
.put(Number160.createHash(locationKey))
.data(contentKey == null ? Number160.ZERO : Number160.createHash(contentKey), data)
.keyPair(keyPair)
.start()
.awaitUninterruptibly();
return futurePut.isSuccess();
}
public static Map<Number640, Data> get(String locationKey) {
FutureGet futureGet = peerDHT
.get(Number160.createHash(locationKey))
.all()
.start()
.awaitUninterruptibly();
if(futureGet != null) {
if(!futureGet.isEmpty()) {
return futureGet.dataMap();
}
}
return null;
}
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();
}
public static boolean remove(String locationKey, String contentKey, KeyPair keyPair) {
FutureRemove futureRemove = peerDHT
.remove(Number160.createHash(locationKey))
.keyPair(keyPair)
.contentKey(contentKey == null ? null : Number160.createHash(contentKey))
.start()
.awaitUninterruptibly();
return futureRemove.isRemoved();
}
}

View File

@ -1,48 +0,0 @@
package io.github.chronosx88.influence.helpers;
import com.sleepycat.bind.EntryBinding;
import com.sleepycat.je.DatabaseEntry;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Serializer<T> implements EntryBinding<T> {
public byte[] serialize(T object) {
ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArray);
objectOutputStream.writeObject(object);
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
return byteArray.toByteArray();
}
public T deserialize(byte[] serializedObject) {
if(serializedObject == null)
return null;
ByteArrayInputStream inputStream = new ByteArrayInputStream(serializedObject);
Object object = null;
try {
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
object = objectInputStream.readObject();
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
return (T) object;
}
@Override
public T entryToObject(DatabaseEntry databaseEntry) {
return deserialize(databaseEntry.getData());
}
@Override
public void objectToEntry(T object, DatabaseEntry databaseEntry) {
databaseEntry.setData(serialize(object));
}
}

View File

@ -1,290 +0,0 @@
package io.github.chronosx88.influence.helpers
import com.sleepycat.collections.StoredSortedMap
import com.sleepycat.je.Database
import com.sleepycat.je.DatabaseConfig
import com.sleepycat.je.Environment
import com.sleepycat.je.EnvironmentConfig
import io.github.chronosx88.influence.helpers.comparators.CompareLong
import io.github.chronosx88.influence.helpers.comparators.CompareNumber640
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 java.io.File
import java.security.PublicKey
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
class StorageBerkeleyDB(peerId: Number160, path : File, signatureFactory: SignatureFactory) : Storage {
// Core
private val dataMap: StoredSortedMap<Number640, Data>
// Maintenance
private val timeoutMap: StoredSortedMap<Number640, Long>
private val timeoutMapRev: StoredSortedMap<Long, Set<Number640>>
// Protection
private val protectedDomainMap: StoredSortedMap<Number320, PublicKey>
private val protectedEntryMap: StoredSortedMap<Number480, PublicKey>
// Responsibility
private val responsibilityMap: StoredSortedMap<Number160, Number160>
private val responsibilityMapRev: StoredSortedMap<Number160, Set<Number160>>
private val dataMapDB: Database
private val timeoutMapDB: Database
private val timeoutMapRevDB: Database
private val protectedDomainMapDB: Database
private val protectedEntryMapDB: Database
private val responsibilityMapDB: Database
private val responsibilityMapRevDB: Database
private val storageCheckIntervalMillis: Int
private val dbEnvironment: Environment
init {
val envConfig = EnvironmentConfig()
envConfig.allowCreate = true
dbEnvironment = Environment(path, envConfig)
val configMap : HashMap<String, DatabaseConfig> = HashMap()
val compareNumber640 = CompareNumber640()
val compareLong = CompareLong()
configMap["dataMapConfig"] = DatabaseConfig().setBtreeComparator(compareNumber640)
configMap["dataMapConfig"]!!.allowCreate = true
configMap["timeoutMapRevConfig"] = DatabaseConfig().setBtreeComparator(compareLong)
configMap["timeoutMapRevConfig"]!!.allowCreate = true
configMap["other"] = DatabaseConfig()
configMap["other"]!!.allowCreate = true
dataMapDB = dbEnvironment.openDatabase(null, "dataMap_$peerId", configMap["dataMapConfig"])
timeoutMapDB = dbEnvironment.openDatabase(null, "timeoutMap_$peerId", configMap["other"])
timeoutMapRevDB = dbEnvironment.openDatabase(null, "timeoutMapRev_$peerId", configMap["timeoutMapRevConfig"])
protectedDomainMapDB = dbEnvironment.openDatabase(null, "protectedDomainMap_$peerId", configMap["other"])
protectedEntryMapDB = dbEnvironment.openDatabase(null, "protectedEntryMap_$peerId", configMap["other"])
responsibilityMapDB = dbEnvironment.openDatabase(null, "responsibilityMap_$peerId", configMap["other"])
responsibilityMapRevDB = dbEnvironment.openDatabase(null, "responsibilityMapRev_$peerId", configMap["other"])
storageCheckIntervalMillis = 60 * 1000
dataMap = StoredSortedMap(dataMapDB, Serializer<Number640>(), DataSerializer(signatureFactory), true)
timeoutMap = StoredSortedMap(timeoutMapDB, Serializer<Number640>(), Serializer<Long>(), true)
timeoutMapRev = StoredSortedMap(timeoutMapRevDB, Serializer<Long>(), Serializer<Set<Number640>>(), true)
protectedDomainMap = StoredSortedMap(protectedDomainMapDB, Serializer<Number320>(), Serializer<PublicKey>(), true)
protectedEntryMap = StoredSortedMap(protectedEntryMapDB, Serializer<Number480>(), Serializer<PublicKey>(), true)
responsibilityMap = StoredSortedMap(responsibilityMapDB, Serializer<Number160>(), Serializer<Number160>(), true)
responsibilityMapRev = StoredSortedMap(responsibilityMapRevDB, Serializer<Number160>(), Serializer<Set<Number160>>(), true)
}
override fun contains(key: Number640?): Boolean {
return dataMap.containsKey(key)
}
override fun contains(from: Number640?, to: Number640?): Int {
return dataMap.subMap(from, true, to, true).size
}
override fun findContentForResponsiblePeerID(peerID: Number160?): MutableSet<Number160>? {
return responsibilityMapRev[peerID] as MutableSet<Number160>?
}
override fun findPeerIDsForResponsibleContent(locationKey: Number160?): Number160? {
return responsibilityMap[locationKey]
}
override fun put(key: Number640?, value: Data?): Data? {
val oldData = dataMap.put(key, value)
dbEnvironment.sync()
return oldData
}
override fun get(key: Number640?): Data? {
return dataMap[key]
}
override fun remove(key: Number640?, returnData: Boolean): Data? {
val oldData = dataMap.remove(key)
dbEnvironment.sync()
return oldData
}
override fun remove(from: Number640?, to: Number640?): NavigableMap<Number640, Data> {
val tmp = dataMap.subMap(from, true, to, true)
val retVal = TreeMap<Number640, Data>()
for(entry : Map.Entry<Number640, Data> in tmp.entries) {
retVal[entry.key] = entry.value
}
tmp.clear()
dbEnvironment.sync()
return retVal
}
override fun addTimeout(key: Number640, expiration: Long) {
val oldExpiration = timeoutMap.put(key, expiration)
putIfAbsent2(expiration, key)
if (oldExpiration == null) {
return
}
removeRevTimeout(key, oldExpiration)
dbEnvironment.sync()
}
private fun putIfAbsent2(expiration: Long, key: Number640) {
var timeouts = timeoutMapRev[expiration]
//var timeouts : MutableSet<Number640> = timeoutMapRev[expiration] as MutableSet<Number640>
if (timeouts == null) {
timeouts = Collections.newSetFromMap(ConcurrentHashMap())
}
(timeouts as MutableSet).add(key)
timeoutMapRev[expiration] = timeouts
dbEnvironment.sync()
}
private fun removeRevTimeout(key: Number640, expiration: Long?) {
val tmp = timeoutMapRev[expiration] as MutableSet<Number640>?
if (tmp != null) {
tmp.remove(key)
if (tmp.isEmpty()) {
timeoutMapRev.remove(expiration)
} else {
timeoutMapRev[expiration!!] = tmp
}
}
dbEnvironment.sync()
}
override fun updateResponsibilities(locationKey: Number160, peerId: Number160?): Boolean {
val oldPeerID = responsibilityMap.put(locationKey, peerId)
val hasChanged: Boolean
if (oldPeerID != null) {
if (oldPeerID == peerId) {
hasChanged = false
} else {
removeRevResponsibility(oldPeerID, locationKey)
hasChanged = true
}
} else {
hasChanged = true
}
var contentIDs: MutableSet<Number160>? = responsibilityMapRev[peerId] as MutableSet?
if (contentIDs == null) {
contentIDs = HashSet()
}
contentIDs.add(locationKey)
responsibilityMapRev[peerId] = contentIDs
dbEnvironment.sync()
return hasChanged
}
private fun removeRevResponsibility(peerId: Number160, locationKey: Number160) {
val contentIDs = responsibilityMapRev[peerId] as MutableSet
if (contentIDs != null) {
contentIDs.remove(locationKey)
if (contentIDs.isEmpty()) {
responsibilityMapRev.remove(peerId)
} else {
responsibilityMapRev[peerId] = contentIDs
}
}
dbEnvironment.sync()
}
override fun protectDomain(key: Number320?, publicKey: PublicKey?): Boolean {
protectedDomainMap[key] = publicKey
dbEnvironment.sync()
return true
}
override fun storageCheckIntervalMillis(): Int {
return storageCheckIntervalMillis
}
override fun isDomainProtectedByOthers(key: Number320?, publicKey: PublicKey?): Boolean {
val other = protectedDomainMap[key] ?: return false
return other != publicKey
}
override fun removeTimeout(key: Number640) {
val expiration = timeoutMap.remove(key) ?: return
removeRevTimeout(key, expiration)
timeoutMapDB.sync()
dbEnvironment.sync()
}
override fun removeResponsibility(locationKey: Number160) {
val peerId = responsibilityMap.remove(locationKey)
if (peerId != null) {
removeRevResponsibility(peerId, locationKey)
}
dbEnvironment.sync()
}
override fun protectEntry(key: Number480?, publicKey: PublicKey?): Boolean {
protectedEntryMap[key] = publicKey
dbEnvironment.sync()
return true
}
override fun map(): NavigableMap<Number640, Data> {
val retVal = TreeMap<Number640, Data>()
for ((key, value) in dataMap) {
retVal[key] = value
}
return retVal
}
override fun isEntryProtectedByOthers(key: Number480?, publicKey: PublicKey?): Boolean {
val other = protectedEntryMap[key] ?: return false
return other != publicKey
}
override fun subMap(from: Number640?, to: Number640?, limit: Int, ascending: Boolean): NavigableMap<Number640, Data> {
val tmp = dataMap.subMap(from, true, to, true)
val descendingMap = TreeMap<Number640, Data>(tmp).descendingMap()
val retVal = TreeMap<Number640, Data>()
if (limit < 0) {
for ((key, value) in if (ascending) tmp else descendingMap) {
retVal[key] = value
}
} else {
val limit1 = Math.min(limit, tmp.size)
val iterator = if (ascending)
tmp.entries.iterator()
else
descendingMap.entries.iterator()
var i = 0
while (iterator.hasNext() && i < limit1) {
val entry = iterator.next()
retVal[entry.key] = entry.value
i++
}
}
return retVal
}
override fun subMapTimeout(to: Long): MutableCollection<Number640> {
val tmp = timeoutMapRev.subMap(0L, to)
val toRemove = ArrayList<Number640>()
for (set in tmp.values) {
toRemove.addAll(set)
}
return toRemove
}
override fun close() {
dataMapDB.close()
timeoutMapDB.close()
timeoutMapRevDB.close()
protectedDomainMapDB.close()
protectedEntryMapDB.close()
responsibilityMapDB.close()
responsibilityMapRevDB.close()
dbEnvironment.close()
}
}

View File

@ -1,8 +0,0 @@
package io.github.chronosx88.influence.helpers.actions;
public class NetworkActions {
public static final int CREATE_CHAT = 0x0;
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,17 +0,0 @@
package io.github.chronosx88.influence.helpers.actions;
public class UIActions {
public static final int BOOTSTRAP_NOT_SPECIFIED = 0x0;
public static final int NETWORK_ERROR = 0x1;
public static final int BOOTSTRAP_SUCCESS = 0x2;
public static final int PORT_FORWARDING_ERROR = 0x3;
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 SUCCESSFUL_CREATE_CHAT = 0x8;
public static final int MESSAGE_RECEIVED = 0x9;
public static final int NODE_IS_OFFLINE = 0x10;
public static final int USERNAME_ISNT_AVAILABLE = 0x11;
public static final int USERNAME_AVAILABLE = 0x12;
}

View File

@ -1,16 +0,0 @@
package io.github.chronosx88.influence.helpers.comparators;
import java.io.Serializable;
import java.util.Comparator;
import io.github.chronosx88.influence.helpers.Serializer;
public class CompareLong implements Comparator<byte[]>, Serializable {
@Override
public int compare(byte[] o1, byte[] o2) {
Serializer<Long> serializer = new Serializer<>();
Long num1 = serializer.deserialize(o1);
Long num2 = serializer.deserialize(o2);
return num1.compareTo(num2);
}
}

View File

@ -1,20 +0,0 @@
package io.github.chronosx88.influence.helpers.comparators;
import net.tomp2p.peers.Number640;
import java.io.Serializable;
import java.util.Comparator;
import io.github.chronosx88.influence.helpers.Serializer;
public class CompareNumber640 implements Comparator<byte[]>, Serializable {
@Override
public int compare(byte[] o1, byte[] o2) {
Serializer<Number640> serializer = new Serializer<>();
Number640 num1 = serializer.deserialize(o1);
Number640 num2 = serializer.deserialize(o2);
return num1.compareTo(num2);
}
}

View File

@ -18,7 +18,6 @@ import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.KeyPairManager;
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.NetworkActions;
import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.models.JoinChatMessage;
@ -159,9 +158,9 @@ public class ChatLogic implements CoreContracts.IChatLogicContract {
if(messages.size() > 10) {
String messageID = UUID.randomUUID().toString();
try {
P2PUtils.put(chatEntity.chatID + "_messages" + chunkID, messageID, new Data(gson.toJson(new NextChunkReference(messageID, AppHelper.getPeerID(), AppHelper.getPeerID(), System.currentTimeMillis(), chatEntity.chunkCursor+1))));
int nextChunkCursor = chatEntity.chunkCursor + 1;
P2PUtils.put(chatEntity.chatID + "_messages" + chunkID, messageID, new Data(gson.toJson(new NextChunkReference(messageID, AppHelper.getPeerID(), AppHelper.getPeerID(), System.currentTimeMillis(), nextChunkCursor))));
P2PUtils.put(chatEntity.chatID + "_newMessage", null, new Data(messageID));
LocalDBWrapper.updateChatEntity(chatEntity);
} catch (IOException e) {
e.printStackTrace();
}

View File

@ -45,12 +45,10 @@ import java.util.UUID;
import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.JVMShutdownHook;
import io.github.chronosx88.influence.helpers.KeyPairManager;
import io.github.chronosx88.influence.helpers.LocalDBWrapper;
import io.github.chronosx88.influence.helpers.NetworkHandler;
import io.github.chronosx88.influence.helpers.ObservableUtils;
import io.github.chronosx88.influence.helpers.P2PUtils;
import io.github.chronosx88.influence.helpers.StorageBerkeleyDB;
import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.models.ChatMetadata;

View File

@ -5,7 +5,6 @@ import io.github.chronosx88.influence.contracts.CoreContracts
import io.github.chronosx88.influence.helpers.AppHelper
import io.github.chronosx88.influence.helpers.KeyPairManager
import io.github.chronosx88.influence.helpers.ObservableUtils
import io.github.chronosx88.influence.helpers.P2PUtils
import io.github.chronosx88.influence.helpers.actions.UIActions
import net.tomp2p.peers.Number640
import net.tomp2p.storage.Data

View File

@ -1,46 +0,0 @@
package io.github.chronosx88.influence.models;
import java.io.Serializable;
/**
* Абстрактный класс-модель для любых сообщений, которые передаются по DHT-сети
*/
public class BasicNetworkMessage implements Serializable {
private int action;
private String messageID;
private String senderID;
private String username;
private long timestamp;
public BasicNetworkMessage() {
//
}
public BasicNetworkMessage(int action, String messageID, String senderID, String username, long timestamp) {
this.action = action;
this.senderID = senderID;
this.username = username;
this.messageID = messageID;
this.timestamp = timestamp;
}
public int getAction() {
return action;
}
public String getSenderID() {
return senderID;
}
public String getUsername() {
return username;
}
public String getMessageID() {
return messageID;
}
public long getTimestamp() {
return timestamp;
}
}

View File

@ -1,21 +0,0 @@
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

@ -1,40 +0,0 @@
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

@ -1,19 +0,0 @@
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,24 +0,0 @@
package io.github.chronosx88.influence.models;
import java.io.Serializable;
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
public class NewChatRequestMessage extends BasicNetworkMessage implements Serializable {
private String chatID;
private int chunkID;
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

@ -1,18 +0,0 @@
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,10 +0,0 @@
package io.github.chronosx88.influence.models
import net.tomp2p.peers.PeerAddress
import java.io.Serializable
/**
* Класс-модель публичного профиля для размещения в DHT-сети
*/
data class PublicUserProfile(var userName: String?, var peerAddress: PeerAddress?) : Serializable

View File

@ -1,30 +0,0 @@
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

@ -14,14 +14,14 @@ public interface ChatDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
void addChat(ChatEntity chatEntity);
@Query("DELETE FROM chats WHERE chatID = :chatID")
void deleteChat(String chatID);
@Query("DELETE FROM chats WHERE jid = :jid")
void deleteChat(String jid);
@Query("SELECT * FROM chats")
List<ChatEntity> getAllChats();
@Query("SELECT * FROM chats WHERE chatID = :chatID")
List<ChatEntity> getChatByChatID(String chatID);
@Query("SELECT * FROM chats WHERE jid = :jid")
List<ChatEntity> getChatByChatID(String jid);
@Update
void updateChat(ChatEntity chat);

View File

@ -12,19 +12,19 @@ import io.github.chronosx88.influence.models.roomEntities.MessageEntity;
@Dao
public interface MessageDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
void insertMessage(MessageEntity chatModel);
long insertMessage(MessageEntity chatModel);
@Query("DELETE FROM messages WHERE messageID = :messageID")
void deleteMessage(String messageID);
@Query("DELETE FROM messages WHERE chatID = :chatID")
void deleteMessagesByChatID(String chatID);
@Query("DELETE FROM messages WHERE jid = :jid")
void deleteMessagesByChatID(String jid);
@Query("SELECT * FROM messages WHERE chatID = :chatID")
List<MessageEntity> getMessagesByChatID(String chatID);
@Query("SELECT * FROM messages WHERE jid = :jid")
List<MessageEntity> getMessagesByChatID(String jid);
@Query("SELECT * FROM messages WHERE messageID = :messageID")
List<MessageEntity> getMessageByID(String messageID);
List<MessageEntity> getMessageByID(long messageID);
@Update
void updateMessage(MessageEntity message);

View File

@ -1,7 +1,5 @@
package io.github.chronosx88.influence.models.roomEntities;
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
@ -9,19 +7,11 @@ import androidx.room.PrimaryKey;
@Entity(tableName = "chats")
public class ChatEntity {
@PrimaryKey @NonNull public String chatID;
@ColumnInfo public String name;
@ColumnInfo public String metadataRef;
@ColumnInfo public String membersRef;
@ColumnInfo public ArrayList<String> bannedUsers;
@ColumnInfo public int chunkCursor;
@PrimaryKey @NonNull public String jid;
@ColumnInfo public String chatName;
public ChatEntity(@NonNull String chatID, String name, String metadataRef, String membersRef, ArrayList<String> bannedUsers, int chunkCursor) {
this.chatID = chatID;
this.name = name;
this.metadataRef = metadataRef;
this.membersRef = membersRef;
this.bannedUsers = bannedUsers;
this.chunkCursor = chunkCursor;
public ChatEntity(@NonNull String jid, String chatName) {
this.jid = jid;
this.chatName = chatName;
}
}

View File

@ -7,22 +7,17 @@ import androidx.room.PrimaryKey;
@Entity(tableName = "messages")
public class MessageEntity {
@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
@PrimaryKey(autoGenerate = true) public long messageID; // Global message ID
@ColumnInfo public String jid; // Chat ID
@ColumnInfo public String senderJid;
@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 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.senderID = senderID;
this.username = username;
public MessageEntity(String jid, String senderJid, long timestamp, String text, boolean isSent, boolean isRead) {
this.jid = jid;
this.senderJid = senderJid;
this.timestamp = timestamp;
this.text = text;
this.isSent = isSent;

View File

@ -0,0 +1,157 @@
/*
* Copyright (C) 2019 ChronosX88
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package io.github.chronosx88.influence.views;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.XMPPConnectionService;
import io.github.chronosx88.influence.contracts.CoreContracts;
import io.github.chronosx88.influence.helpers.AppHelper;
public class LoginActivity extends AppCompatActivity implements CoreContracts.ILoginViewContract {
private EditText jidEditText;
private EditText passwordEditText;
private Button signInButton;
private BroadcastReceiver broadcastReceiver;
private ProgressDialog progressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
jidEditText = findViewById(R.id.login_jid);
passwordEditText = findViewById(R.id.login_password);
signInButton = findViewById(R.id.sign_in_button);
progressDialog = new ProgressDialog(LoginActivity.this);
signInButton.setOnClickListener((v) -> {
if(checkLoginCredentials()) {
saveLoginCredentials();
doLogin();
}
});
}
@Override
public void loadingScreen(boolean state) {
if(state)
progressDialog.show();
else
progressDialog.dismiss();
}
@Override
protected void onResume() {
super.onResume();
broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case XMPPConnectionService.INTENT_AUTHENTICATED: {
loadingScreen(false);
finish();
break;
}
case XMPPConnectionService.INTENT_AUTHENTICATION_FAILED: {
loadingScreen(false);
passwordEditText.setError("Invalid JID/Password");
break;
}
}
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(XMPPConnectionService.INTENT_AUTHENTICATED);
filter.addAction(XMPPConnectionService.INTENT_AUTHENTICATION_FAILED);
this.registerReceiver(broadcastReceiver, filter);
}
@Override
protected void onPause() {
super.onPause();
this.unregisterReceiver(broadcastReceiver);
}
private boolean checkLoginCredentials() {
jidEditText.setError(null);
passwordEditText.setError(null);
String jid = jidEditText.getText().toString();
String password = passwordEditText.getText().toString();
boolean cancel = false;
View focusView = null;
if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
passwordEditText.setError("Invalid password");
focusView = passwordEditText;
cancel = true;
}
if (TextUtils.isEmpty(jid)) {
jidEditText.setError("Field is required!");
focusView = jidEditText;
cancel = true;
} else if (!isEmailValid(jid)) {
jidEditText.setError("Invalid JID");
focusView = jidEditText;
cancel = true;
}
if (cancel) {
focusView.requestFocus();
return false;
} else {
return true;
}
}
private boolean isEmailValid(String email) {
return email.contains("@");
}
private boolean isPasswordValid(String password) {
return password.length() > 4;
}
private void saveLoginCredentials() {
AppHelper.getPreferences().edit()
.putString("jid", jidEditText.getText().toString())
.putString("pass", passwordEditText.getText().toString())
.putBoolean("logged_in", true)
.apply();
}
private void doLogin() {
loadingScreen(true);
startService(new Intent(this, XMPPConnectionService.class));
}
}

View File

@ -32,7 +32,7 @@ import io.github.chronosx88.influence.views.fragments.ChatListFragment;
import io.github.chronosx88.influence.views.fragments.SettingsFragment;
import kotlin.Pair;
public class MainActivity extends AppCompatActivity implements IObserver, CoreContracts.IMainViewContract {
public class MainActivity extends AppCompatActivity implements CoreContracts.IMainViewContract {
private CoreContracts.IMainPresenterContract presenter;
private ProgressDialog progressDialog;
@ -88,7 +88,7 @@ public class MainActivity extends AppCompatActivity implements IObserver, CoreCo
.commit();
presenter = new MainPresenter(this);
AppHelper.getObservable().register(this);
progressDialog = new ProgressDialog(MainActivity.this, R.style.AlertDialogTheme);
progressDialog.setCancelable(false);
@ -101,54 +101,6 @@ public class MainActivity extends AppCompatActivity implements IObserver, CoreCo
protected void onDestroy() {
super.onDestroy();
presenter.onDestroy();
AppHelper.getObservable().unregister(this);
}
@Override
public void handleEvent(JsonObject object) {
switch (object.get("action").getAsInt()) {
case UIActions.BOOTSTRAP_NOT_SPECIFIED: {
runOnUiThread(() -> {
progressDialog.dismiss();
Toast.makeText(this, "Bootstrap-нода не указана. Прерываю подключение к сети...", Toast.LENGTH_LONG).show();
});
break;
}
case UIActions.NETWORK_ERROR: {
runOnUiThread(() -> {
progressDialog.dismiss();
Toast.makeText(this, "Ошибка сети. Возможно, нода недоступна, или у вас отсутствует Интернет.", Toast.LENGTH_LONG).show();
});
break;
}
case UIActions.BOOTSTRAP_SUCCESS: {
runOnUiThread(() -> {
progressDialog.dismiss();
Toast.makeText(this, "Нода успешно запущена!", Toast.LENGTH_LONG).show();
});
break;
}
case UIActions.PORT_FORWARDING_ERROR: {
runOnUiThread(() -> {
Toast.makeText(this, "Проблемы с пробросом портов. Возможно, у вас не настроен uPnP.", Toast.LENGTH_LONG).show();
});
break;
}
case UIActions.BOOTSTRAP_ERROR: {
runOnUiThread(() -> {
progressDialog.dismiss();
Toast.makeText(this, "Не удалось подключиться к бутстрап-ноде.", Toast.LENGTH_LONG).show();
});
break;
}
case UIActions.RELAY_CONNECTION_ERROR: {
runOnUiThread(() -> {
progressDialog.dismiss();
Toast.makeText(this, "Не удалось подключиться к relay-ноде.", Toast.LENGTH_LONG).show();
});
break;
}
}
}
@Override

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2019 ChronosX88
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".views.LoginActivity">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/login_jid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="JabberID"
android:inputType="textEmailAddress"
android:maxLines="1"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/login_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:imeActionLabel="SIGN IN"
android:imeOptions="actionUnspecified"
android:inputType="textPassword"
android:maxLines="1"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/sign_in_button"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="SIGN IN"
android:textStyle="bold" />
</LinearLayout>