This commit is contained in:
Brice-W 2021-04-29 13:53:50 +10:00
commit 76c253ee77
193 changed files with 1341 additions and 17144 deletions

View File

@ -0,0 +1,20 @@
package org.thoughtcrime.securesms
import android.util.Log
import nl.komponents.kovenant.Kovenant
import nl.komponents.kovenant.jvm.asDispatcher
import org.session.libsignal.utilities.ThreadUtils
import java.util.concurrent.Executors
object AppContext {
fun configureKovenant() {
Kovenant.context {
callbackContext.dispatcher = Executors.newSingleThreadExecutor().asDispatcher()
workerContext.dispatcher = ThreadUtils.executorPool.asDispatcher()
multipleCompletion = { v1, v2 ->
Log.d("Loki", "Promise resolved more than once (first with $v1, then with $v2); ignoring $v2.")
}
}
}
}

View File

@ -30,15 +30,17 @@ import androidx.lifecycle.ProcessLifecycleOwner;
import androidx.multidex.MultiDexApplication; import androidx.multidex.MultiDexApplication;
import org.conscrypt.Conscrypt; import org.conscrypt.Conscrypt;
import org.session.libsession.messaging.MessagingConfiguration; import org.session.libsession.messaging.MessagingModuleConfiguration;
import org.session.libsession.messaging.avatars.AvatarHelper; import org.session.libsession.messaging.avatars.AvatarHelper;
import org.session.libsession.messaging.file_server.FileServerAPI;
import org.session.libsession.messaging.jobs.JobQueue; import org.session.libsession.messaging.jobs.JobQueue;
import org.session.libsession.messaging.opengroups.OpenGroupAPI; import org.session.libsession.messaging.mentions.MentionsManager;
import org.session.libsession.messaging.open_groups.OpenGroupAPI;
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPoller; import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPoller;
import org.session.libsession.messaging.sending_receiving.pollers.Poller; import org.session.libsession.messaging.sending_receiving.pollers.Poller;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.Address;
import org.session.libsession.snode.SnodeConfiguration; import org.session.libsession.snode.SnodeModule;
import org.session.libsession.utilities.IdentityKeyUtil; import org.session.libsession.utilities.IdentityKeyUtil;
import org.session.libsession.utilities.SSKEnvironment; import org.session.libsession.utilities.SSKEnvironment;
import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.TextSecurePreferences;
@ -47,12 +49,7 @@ import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWr
import org.session.libsession.utilities.dynamiclanguage.LocaleParser; import org.session.libsession.utilities.dynamiclanguage.LocaleParser;
import org.session.libsession.utilities.preferences.ProfileKeyUtil; import org.session.libsession.utilities.preferences.ProfileKeyUtil;
import org.session.libsignal.service.api.util.StreamDetails; import org.session.libsignal.service.api.util.StreamDetails;
import org.session.libsignal.service.loki.api.PushNotificationAPI; import org.session.libsignal.service.loki.LokiAPIDatabaseProtocol;
import org.session.libsignal.service.loki.api.SnodeAPI;
import org.session.libsignal.service.loki.api.SwarmAPI;
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI;
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol;
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;
import org.signal.aesgcmprovider.AesGcmProvider; import org.signal.aesgcmprovider.AesGcmProvider;
import org.thoughtcrime.securesms.components.TypingStatusSender; import org.thoughtcrime.securesms.components.TypingStatusSender;
@ -106,6 +103,7 @@ import dagger.ObjectGraph;
import kotlin.Unit; import kotlin.Unit;
import kotlinx.coroutines.Job; import kotlinx.coroutines.Job;
import network.loki.messenger.BuildConfig; import network.loki.messenger.BuildConfig;
import nl.komponents.kovenant.Kovenant;
import static nl.komponents.kovenant.android.KovenantAndroid.startKovenant; import static nl.komponents.kovenant.android.KovenantAndroid.startKovenant;
import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant; import static nl.komponents.kovenant.android.KovenantAndroid.stopKovenant;
@ -166,6 +164,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
ProcessLifecycleOwner.get().getLifecycle().addObserver(this); ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
// Loki // Loki
// ======== // ========
AppContext.INSTANCE.configureKovenant();
messageNotifier = new OptimizedMessageNotifier(new DefaultMessageNotifier()); messageNotifier = new OptimizedMessageNotifier(new DefaultMessageNotifier());
broadcaster = new Broadcaster(this); broadcaster = new Broadcaster(this);
threadNotificationHandler = new Handler(Looper.getMainLooper()); threadNotificationHandler = new Handler(Looper.getMainLooper());
@ -173,17 +172,14 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(this); LokiThreadDatabase threadDB = DatabaseFactory.getLokiThreadDatabase(this);
LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this); LokiUserDatabase userDB = DatabaseFactory.getLokiUserDatabase(this);
String userPublicKey = TextSecurePreferences.getLocalNumber(this); String userPublicKey = TextSecurePreferences.getLocalNumber(this);
MessagingConfiguration.Companion.configure(this, MessagingModuleConfiguration.Companion.configure(this,
DatabaseFactory.getStorage(this), DatabaseFactory.getStorage(this),
DatabaseFactory.getAttachmentProvider(this), DatabaseFactory.getAttachmentProvider(this),
new SessionProtocolImpl(this)); new SessionProtocolImpl(this));
SnodeConfiguration.Companion.configure(apiDB, broadcaster); SnodeModule.Companion.configure(apiDB, broadcaster);
if (userPublicKey != null) { if (userPublicKey != null) {
SwarmAPI.Companion.configureIfNeeded(apiDB); MentionsManager.Companion.configureIfNeeded(userPublicKey, userDB);
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
MentionsManager.Companion.configureIfNeeded(userPublicKey, threadDB, userDB);
} }
PushNotificationAPI.Companion.configureIfNeeded(BuildConfig.DEBUG);
setUpStorageAPIIfNeeded(); setUpStorageAPIIfNeeded();
resubmitProfilePictureIfNeeded(); resubmitProfilePictureIfNeeded();
publicChatManager = new PublicChatManager(this); publicChatManager = new PublicChatManager(this);
@ -428,7 +424,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize(); byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize();
LokiAPIDatabaseProtocol apiDB = DatabaseFactory.getLokiAPIDatabase(this); LokiAPIDatabaseProtocol apiDB = DatabaseFactory.getLokiAPIDatabase(this);
FileServerAPI.Companion.configure(userPublicKey, userPrivateKey, apiDB); FileServerAPI.Companion.configure(userPublicKey, userPrivateKey, apiDB);
org.session.libsession.messaging.fileserver.FileServerAPI.Companion.configure(userPublicKey, userPrivateKey, apiDB);
return true; return true;
} }
@ -458,13 +453,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
String userPublicKey = TextSecurePreferences.getLocalNumber(this); String userPublicKey = TextSecurePreferences.getLocalNumber(this);
if (userPublicKey == null) return; if (userPublicKey == null) return;
if (poller != null) { if (poller != null) {
SnodeAPI.shared.setUserPublicKey(userPublicKey);
poller.setUserPublicKey(userPublicKey); poller.setUserPublicKey(userPublicKey);
return; return;
} }
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this); LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this);
SwarmAPI.Companion.configureIfNeeded(apiDB);
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
poller = new Poller(); poller = new Poller();
closedGroupPoller = new ClosedGroupPoller(); closedGroupPoller = new ClosedGroupPoller();
} }

View File

@ -9,7 +9,7 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;

View File

@ -40,6 +40,7 @@ import androidx.loader.content.Loader;
import org.session.libsession.messaging.messages.visible.LinkPreview; import org.session.libsession.messaging.messages.visible.LinkPreview;
import org.session.libsession.messaging.messages.visible.Quote; import org.session.libsession.messaging.messages.visible.Quote;
import org.session.libsession.messaging.messages.visible.VisibleMessage; import org.session.libsession.messaging.messages.visible.VisibleMessage;
import org.session.libsession.messaging.open_groups.OpenGroup;
import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.messaging.sending_receiving.MessageSender;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.thoughtcrime.securesms.MessageDetailsRecipientAdapter.RecipientDeliveryStatus; import org.thoughtcrime.securesms.MessageDetailsRecipientAdapter.RecipientDeliveryStatus;
@ -64,7 +65,6 @@ import org.thoughtcrime.securesms.util.DateUtils;
import org.session.libsession.utilities.ExpirationUtil; import org.session.libsession.utilities.ExpirationUtil;
import org.session.libsession.utilities.Util; import org.session.libsession.utilities.Util;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.sql.Date; import java.sql.Date;
@ -263,7 +263,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
} }
toFrom.setText(toFromRes); toFrom.setText(toFromRes);
long threadID = messageRecord.getThreadId(); long threadID = messageRecord.getThreadId();
PublicChat openGroup = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadID); OpenGroup openGroup = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadID);
if (openGroup != null && messageRecord.isOutgoing()) { if (openGroup != null && messageRecord.isOutgoing()) {
toFrom.setVisibility(View.GONE); toFrom.setVisibility(View.GONE);
separator.setVisibility(View.GONE); separator.setVisibility(View.GONE);

View File

@ -5,6 +5,7 @@ import android.text.TextUtils
import com.google.protobuf.ByteString import com.google.protobuf.ByteString
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.MessageDataProvider
import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.messaging.sending_receiving.attachments.* import org.session.libsession.messaging.sending_receiving.attachments.*
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.utilities.DotNetAPI import org.session.libsession.messaging.utilities.DotNetAPI
@ -26,7 +27,6 @@ import org.thoughtcrime.securesms.util.MediaUtil
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), MessageDataProvider { class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), MessageDataProvider {
override fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? { override fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? {
@ -104,6 +104,10 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
return smsDatabase.isOutgoingMessage(timestamp) || mmsDatabase.isOutgoingMessage(timestamp) return smsDatabase.isOutgoingMessage(timestamp) || mmsDatabase.isOutgoingMessage(timestamp)
} }
override fun getOpenGroup(threadID: Long): OpenGroup? {
return null // TODO: Implement
}
override fun updateAttachmentAfterUploadSucceeded(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) { override fun updateAttachmentAfterUploadSucceeded(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) {
val database = DatabaseFactory.getAttachmentDatabase(context) val database = DatabaseFactory.getAttachmentDatabase(context)
val databaseAttachment = getDatabaseAttachment(attachmentId) ?: return val databaseAttachment = getDatabaseAttachment(attachmentId) ?: return

View File

@ -30,7 +30,7 @@ import org.thoughtcrime.securesms.loki.utilities.MentionUtilities;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.TextSecurePreferences;

View File

@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.ImageSlide; import org.thoughtcrime.securesms.mms.ImageSlide;
import org.thoughtcrime.securesms.mms.SlidesClickedListener; import org.thoughtcrime.securesms.mms.SlidesClickedListener;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
import network.loki.messenger.R; import network.loki.messenger.R;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;

View File

@ -21,6 +21,7 @@ import androidx.annotation.RequiresApi;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.session.libsession.messaging.open_groups.OpenGroup;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
@ -35,7 +36,6 @@ import org.session.libsession.messaging.threads.recipients.RecipientModifiedList
import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.ThemeUtil; import org.session.libsession.utilities.ThemeUtil;
import org.session.libsession.utilities.Util; import org.session.libsession.utilities.Util;
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
import java.util.List; import java.util.List;
@ -201,7 +201,7 @@ public class QuoteView extends FrameLayout implements RecipientModifiedListener
long threadID = DatabaseFactory.getThreadDatabase(getContext()).getOrCreateThreadIdFor(conversationRecipient); long threadID = DatabaseFactory.getThreadDatabase(getContext()).getOrCreateThreadIdFor(conversationRecipient);
String senderHexEncodedPublicKey = author.getAddress().serialize(); String senderHexEncodedPublicKey = author.getAddress().serialize();
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadID); OpenGroup publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadID);
if (senderHexEncodedPublicKey.equalsIgnoreCase(TextSecurePreferences.getLocalNumber(getContext()))) { if (senderHexEncodedPublicKey.equalsIgnoreCase(TextSecurePreferences.getLocalNumber(getContext()))) {
quoteeDisplayName = TextSecurePreferences.getProfileName(getContext()); quoteeDisplayName = TextSecurePreferences.getProfileName(getContext());
} else if (publicChat != null) { } else if (publicChat != null) {

View File

@ -85,17 +85,17 @@ import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode; import org.greenrobot.eventbus.ThreadMode;
import org.session.libsession.messaging.mentions.MentionsManager;
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate; import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate;
import org.session.libsession.messaging.messages.visible.VisibleMessage; import org.session.libsession.messaging.messages.visible.VisibleMessage;
import org.session.libsession.messaging.open_groups.OpenGroup;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.threads.DistributionTypes; import org.session.libsession.messaging.threads.DistributionTypes;
import org.session.libsession.utilities.GroupUtil; import org.session.libsession.utilities.GroupUtil;
import org.session.libsession.utilities.MediaTypes; import org.session.libsession.utilities.MediaTypes;
import org.session.libsignal.libsignal.InvalidMessageException; import org.session.libsignal.libsignal.InvalidMessageException;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.loki.api.opengroups.PublicChat; import org.session.libsignal.service.loki.Mention;
import org.session.libsignal.service.loki.utilities.mentions.Mention;
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager;
import org.session.libsignal.service.loki.utilities.HexEncodingKt; import org.session.libsignal.service.loki.utilities.HexEncodingKt;
import org.session.libsignal.service.loki.utilities.PublicKeyValidation; import org.session.libsignal.service.loki.utilities.PublicKeyValidation;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
@ -183,7 +183,7 @@ import org.session.libsession.utilities.ServiceUtil;
import org.session.libsession.utilities.Util; import org.session.libsession.utilities.Util;
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact; import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
import org.session.libsession.messaging.threads.GroupRecord; import org.session.libsession.messaging.threads.GroupRecord;
import org.session.libsession.utilities.ExpirationUtil; import org.session.libsession.utilities.ExpirationUtil;
@ -376,7 +376,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(threadId, this); MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(threadId, this);
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId); OpenGroup publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
if (publicChat != null) { if (publicChat != null) {
// Request open group info update and handle the successful result in #onOpenGroupInfoUpdated(). // Request open group info update and handle the successful result in #onOpenGroupInfoUpdated().
PublicChatInfoUpdateWorker.scheduleInstant(this, publicChat.getServer(), publicChat.getChannel()); PublicChatInfoUpdateWorker.scheduleInstant(this, publicChat.getServer(), publicChat.getChannel());
@ -1399,7 +1399,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
public void onOpenGroupInfoUpdated(OpenGroupUtilities.GroupInfoUpdatedEvent event) { public void onOpenGroupInfoUpdated(OpenGroupUtilities.GroupInfoUpdatedEvent event) {
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId); OpenGroup publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
if (publicChat != null && if (publicChat != null &&
publicChat.getChannel() == event.getChannel() && publicChat.getChannel() == event.getChannel() &&
publicChat.getServer().equals(event.getUrl())) { publicChat.getServer().equals(event.getUrl())) {
@ -2337,7 +2337,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
muteIndicatorImageView.setVisibility(View.VISIBLE); muteIndicatorImageView.setVisibility(View.VISIBLE);
subtitleTextView.setText("Muted until " + DateUtils.getFormattedDateTime(recipient.mutedUntil, "EEE, MMM d, yyyy HH:mm", Locale.getDefault())); subtitleTextView.setText("Muted until " + DateUtils.getFormattedDateTime(recipient.mutedUntil, "EEE, MMM d, yyyy HH:mm", Locale.getDefault()));
} else if (recipient.isGroupRecipient() && recipient.getName() != null && !recipient.getName().equals("Session Updates") && !recipient.getName().equals("Loki News")) { } else if (recipient.isGroupRecipient() && recipient.getName() != null && !recipient.getName().equals("Session Updates") && !recipient.getName().equals("Loki News")) {
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId); OpenGroup publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId);
if (publicChat != null) { if (publicChat != null) {
Integer userCount = DatabaseFactory.getLokiAPIDatabase(this).getUserCount(publicChat.getChannel(), publicChat.getServer()); Integer userCount = DatabaseFactory.getLokiAPIDatabase(this).getUserCount(publicChat.getChannel(), publicChat.getServer());
if (userCount == null) { userCount = 0; } if (userCount == null) { userCount = 0; }

View File

@ -60,7 +60,8 @@ import com.annimon.stream.Stream;
import org.session.libsession.messaging.messages.control.DataExtractionNotification; import org.session.libsession.messaging.messages.control.DataExtractionNotification;
import org.session.libsession.messaging.messages.visible.Quote; import org.session.libsession.messaging.messages.visible.Quote;
import org.session.libsession.messaging.messages.visible.VisibleMessage; import org.session.libsession.messaging.messages.visible.VisibleMessage;
import org.session.libsession.messaging.opengroups.OpenGroupAPI; import org.session.libsession.messaging.open_groups.OpenGroup;
import org.session.libsession.messaging.open_groups.OpenGroupAPI;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.MessageDetailsActivity; import org.thoughtcrime.securesms.MessageDetailsActivity;
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity; import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
@ -88,13 +89,12 @@ import org.thoughtcrime.securesms.permissions.Permissions;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.messaging.sending_receiving.MessageSender;
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage; import org.session.libsession.messaging.messages.signal.OutgoingTextMessage;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.SaveAttachmentTask; import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration; import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.session.libsession.utilities.task.ProgressDialogAsyncTask; import org.session.libsession.utilities.task.ProgressDialogAsyncTask;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util; import org.session.libsession.utilities.Util;
@ -396,7 +396,7 @@ public class ConversationFragment extends Fragment
boolean isGroupChat = recipient.isGroupRecipient(); boolean isGroupChat = recipient.isGroupRecipient();
if (isGroupChat) { if (isGroupChat) {
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId); OpenGroup publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId);
boolean isPublicChat = (publicChat != null); boolean isPublicChat = (publicChat != null);
int selectedMessageCount = messageRecords.size(); int selectedMessageCount = messageRecords.size();
boolean areAllSentByUser = true; boolean areAllSentByUser = true;
@ -508,7 +508,7 @@ public class ConversationFragment extends Fragment
builder.setMessage(getActivity().getResources().getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messagesCount, messagesCount)); builder.setMessage(getActivity().getResources().getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messagesCount, messagesCount));
builder.setCancelable(true); builder.setCancelable(true);
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId); OpenGroup publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId);
builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() { builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
@Override @Override
@ -591,7 +591,7 @@ public class ConversationFragment extends Fragment
builder.setTitle(R.string.ConversationFragment_ban_selected_user); builder.setTitle(R.string.ConversationFragment_ban_selected_user);
builder.setCancelable(true); builder.setCancelable(true);
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId); OpenGroup publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId);
builder.setPositiveButton(R.string.ban, (dialog, which) -> { builder.setPositiveButton(R.string.ban, (dialog, which) -> {
ConversationAdapter chatAdapter = getListAdapter(); ConversationAdapter chatAdapter = getListAdapter();

View File

@ -54,10 +54,11 @@ import com.annimon.stream.Stream;
import org.session.libsession.messaging.jobs.AttachmentDownloadJob; import org.session.libsession.messaging.jobs.AttachmentDownloadJob;
import org.session.libsession.messaging.jobs.JobQueue; import org.session.libsession.messaging.jobs.JobQueue;
import org.session.libsession.messaging.opengroups.OpenGroupAPI; import org.session.libsession.messaging.open_groups.OpenGroup;
import org.session.libsession.messaging.open_groups.OpenGroupAPI;
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress; import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress;
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener; import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener;
import org.session.libsession.utilities.GroupUtil; import org.session.libsession.utilities.GroupUtil;
@ -67,7 +68,6 @@ import org.session.libsession.utilities.Util;
import org.session.libsession.utilities.ViewUtil; import org.session.libsession.utilities.ViewUtil;
import org.session.libsession.utilities.views.Stub; import org.session.libsession.utilities.views.Stub;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.BindableConversationItem; import org.thoughtcrime.securesms.BindableConversationItem;
import org.thoughtcrime.securesms.MediaPreviewActivity; import org.thoughtcrime.securesms.MediaPreviewActivity;
@ -724,7 +724,7 @@ public class ConversationItem extends LinearLayout
String publicKey = recipient.getAddress().toString(); String publicKey = recipient.getAddress().toString();
profilePictureView.setPublicKey(publicKey); profilePictureView.setPublicKey(publicKey);
String displayName = recipient.getName(); String displayName = recipient.getName();
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID); OpenGroup publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID);
if (displayName == null && publicChat != null) { if (displayName == null && publicChat != null) {
displayName = DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.getId(), publicKey); displayName = DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.getId(), publicKey);
} }
@ -911,7 +911,7 @@ public class ConversationItem extends LinearLayout
profilePictureView.setVisibility(VISIBLE); profilePictureView.setVisibility(VISIBLE);
int visibility = View.GONE; int visibility = View.GONE;
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(messageRecord.getThreadId()); OpenGroup publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(messageRecord.getThreadId());
if (publicChat != null) { if (publicChat != null) {
boolean isModerator = OpenGroupAPI.isUserModerator(current.getRecipient().getAddress().toString(), publicChat.getChannel(), publicChat.getServer()); boolean isModerator = OpenGroupAPI.isUserModerator(current.getRecipient().getAddress().toString(), publicChat.getChannel(), publicChat.getServer());
visibility = isModerator ? View.VISIBLE : View.GONE; visibility = isModerator ? View.VISIBLE : View.GONE;

View File

@ -14,7 +14,7 @@ import androidx.annotation.ColorInt;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage; import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage;
import org.thoughtcrime.securesms.BindableConversationItem; import org.thoughtcrime.securesms.BindableConversationItem;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt; import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt;

View File

@ -25,10 +25,9 @@ import org.session.libsession.utilities.Util;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer; import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer;
import org.session.libsignal.service.loki.database.LokiOpenGroupDatabaseProtocol; import org.session.libsignal.service.loki.LokiOpenGroupDatabaseProtocol;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
@ -44,6 +43,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
static final String GROUP_ID = "group_id"; static final String GROUP_ID = "group_id";
private static final String TITLE = "title"; private static final String TITLE = "title";
private static final String MEMBERS = "members"; private static final String MEMBERS = "members";
private static final String ZOMBIE_MEMBERS = "zombie_members";
private static final String AVATAR = "avatar"; private static final String AVATAR = "avatar";
private static final String AVATAR_ID = "avatar_id"; private static final String AVATAR_ID = "avatar_id";
private static final String AVATAR_KEY = "avatar_key"; private static final String AVATAR_KEY = "avatar_key";
@ -64,6 +64,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
GROUP_ID + " TEXT, " + GROUP_ID + " TEXT, " +
TITLE + " TEXT, " + TITLE + " TEXT, " +
MEMBERS + " TEXT, " + MEMBERS + " TEXT, " +
ZOMBIE_MEMBERS + " TEXT, " +
AVATAR + " BLOB, " + AVATAR + " BLOB, " +
AVATAR_ID + " INTEGER, " + AVATAR_ID + " INTEGER, " +
AVATAR_KEY + " BLOB, " + AVATAR_KEY + " BLOB, " +
@ -81,7 +82,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
}; };
private static final String[] GROUP_PROJECTION = { private static final String[] GROUP_PROJECTION = {
GROUP_ID, TITLE, MEMBERS, AVATAR, AVATAR_ID, AVATAR_KEY, AVATAR_CONTENT_TYPE, AVATAR_RELAY, AVATAR_DIGEST, GROUP_ID, TITLE, MEMBERS, ZOMBIE_MEMBERS, AVATAR, AVATAR_ID, AVATAR_KEY, AVATAR_CONTENT_TYPE, AVATAR_RELAY, AVATAR_DIGEST,
TIMESTAMP, ACTIVE, MMS, AVATAR_URL, ADMINS TIMESTAMP, ACTIVE, MMS, AVATAR_URL, ADMINS
}; };
@ -162,7 +163,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
} }
public @NonNull List<Recipient> getGroupMembers(String groupId, boolean includeSelf) { public @NonNull List<Recipient> getGroupMembers(String groupId, boolean includeSelf) {
List<Address> members = getCurrentMembers(groupId); List<Address> members = getCurrentMembers(groupId, false);
List<Recipient> recipients = new LinkedList<>(); List<Recipient> recipients = new LinkedList<>();
for (Address member : members) { for (Address member : members) {
@ -177,6 +178,17 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
return recipients; return recipients;
} }
public @NonNull List<Recipient> getGroupZombieMembers(String groupId) {
List<Address> members = getCurrentZombieMembers(groupId);
List<Recipient> recipients = new LinkedList<>();
for (Address member : members) {
recipients.add(Recipient.from(context, member, false));
}
return recipients;
}
public long create(@NonNull String groupId, @Nullable String title, @NonNull List<Address> members, public long create(@NonNull String groupId, @Nullable String title, @NonNull List<Address> members,
@Nullable SignalServiceAttachmentPointer avatar, @Nullable String relay, @Nullable List<Address> admins, @NonNull Long formationTimestamp) @Nullable SignalServiceAttachmentPointer avatar, @Nullable String relay, @Nullable List<Address> admins, @NonNull Long formationTimestamp)
{ {
@ -300,6 +312,16 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
}); });
} }
public void updateZombieMembers(String groupId, List<Address> members) {
Collections.sort(members);
ContentValues contents = new ContentValues();
contents.put(ZOMBIE_MEMBERS, Address.toSerializedList(members, ','));
contents.put(ACTIVE, 1);
databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?",
new String[] {groupId});
}
public void updateAdmins(String groupId, List<Address> admins) { public void updateAdmins(String groupId, List<Address> admins) {
Collections.sort(admins); Collections.sort(admins);
@ -311,7 +333,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
} }
public void removeMember(String groupId, Address source) { public void removeMember(String groupId, Address source) {
List<Address> currentMembers = getCurrentMembers(groupId); List<Address> currentMembers = getCurrentMembers(groupId, false);
currentMembers.remove(source); currentMembers.remove(source);
ContentValues contents = new ContentValues(); ContentValues contents = new ContentValues();
@ -329,17 +351,21 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
}); });
} }
private List<Address> getCurrentMembers(String groupId) { private List<Address> getCurrentMembers(String groupId, boolean zombieMembers) {
Cursor cursor = null; Cursor cursor = null;
String membersColumn = MEMBERS;
if (zombieMembers) membersColumn = ZOMBIE_MEMBERS;
try { try {
cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] {MEMBERS}, cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] {membersColumn},
GROUP_ID + " = ?", GROUP_ID + " = ?",
new String[] {groupId}, new String[] {groupId},
null, null, null); null, null, null);
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
String serializedMembers = cursor.getString(cursor.getColumnIndexOrThrow(MEMBERS)); String serializedMembers = cursor.getString(cursor.getColumnIndexOrThrow(membersColumn));
if (serializedMembers != null && !serializedMembers.isEmpty())
return Address.fromSerializedList(serializedMembers, ','); return Address.fromSerializedList(serializedMembers, ',');
} }
@ -350,6 +376,10 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
} }
} }
private List<Address> getCurrentZombieMembers(String groupId) {
return getCurrentMembers(groupId, true);
}
public boolean isActive(String groupId) { public boolean isActive(String groupId) {
Optional<GroupRecord> record = getGroup(groupId); Optional<GroupRecord> record = getGroup(groupId);
return record.isPresent() && record.get().isActive(); return record.isPresent() && record.get().isActive();

View File

@ -28,7 +28,6 @@ import com.annimon.stream.Collectors;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import com.google.android.mms.pdu_alt.NotificationInd; import com.google.android.mms.pdu_alt.NotificationInd;
import com.google.android.mms.pdu_alt.PduHeaders; import com.google.android.mms.pdu_alt.PduHeaders;
import com.google.protobuf.ByteString;
import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteDatabase;
@ -60,7 +59,7 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId; import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId;
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment;
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact; import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;

View File

@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.database
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import com.google.protobuf.ByteString
import org.session.libsession.messaging.StorageProtocol import org.session.libsession.messaging.StorageProtocol
import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.AttachmentUploadJob
import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.Job
@ -13,18 +12,17 @@ import org.session.libsession.messaging.messages.signal.*
import org.session.libsession.messaging.messages.signal.IncomingTextMessage import org.session.libsession.messaging.messages.signal.IncomingTextMessage
import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.Attachment
import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.messages.visible.VisibleMessage
import org.session.libsession.messaging.opengroups.OpenGroup import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.threads.Address.Companion.fromSerialized import org.session.libsession.messaging.threads.Address.Companion.fromSerialized
import org.session.libsession.messaging.threads.GroupRecord import org.session.libsession.messaging.threads.GroupRecord
import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.messaging.threads.recipients.Recipient
import org.session.libsession.messaging.utilities.UpdateMessageBuilder import org.session.libsession.messaging.utilities.ClosedGroupUpdateMessageData
import org.session.libsession.messaging.utilities.UpdateMessageData
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.IdentityKeyUtil import org.session.libsession.utilities.IdentityKeyUtil
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
@ -34,7 +32,6 @@ import org.session.libsignal.libsignal.util.KeyHelper
import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.libsignal.util.guava.Optional
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.api.messages.SignalServiceGroup
import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
@ -44,7 +41,6 @@ import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
import org.thoughtcrime.securesms.loki.utilities.get import org.thoughtcrime.securesms.loki.utilities.get
import org.thoughtcrime.securesms.loki.utilities.getString import org.thoughtcrime.securesms.loki.utilities.getString
import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.mms.PartAuthority
import java.util.*
class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol {
override fun getUserPublicKey(): String? { override fun getUserPublicKey(): String? {
@ -395,6 +391,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
DatabaseFactory.getGroupDatabase(context).setActive(groupID, value) DatabaseFactory.getGroupDatabase(context).setActive(groupID, value)
} }
override fun getZombieMember(groupID: String): Set<String> {
return DatabaseFactory.getGroupDatabase(context).getGroupZombieMembers(groupID).map { it.address.serialize() }.toHashSet()
}
override fun removeMember(groupID: String, member: Address) { override fun removeMember(groupID: String, member: Address) {
DatabaseFactory.getGroupDatabase(context).removeMember(groupID, member) DatabaseFactory.getGroupDatabase(context).removeMember(groupID, member)
} }
@ -403,10 +403,14 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
DatabaseFactory.getGroupDatabase(context).updateMembers(groupID, members) DatabaseFactory.getGroupDatabase(context).updateMembers(groupID, members)
} }
override fun updateZombieMembers(groupID: String, members: List<Address>) {
DatabaseFactory.getGroupDatabase(context).updateZombieMembers(groupID, members)
}
override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, sentTimestamp: Long) { override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, sentTimestamp: Long) {
val group = SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList()) val group = SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList())
val m = IncomingTextMessage(Address.fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true) val m = IncomingTextMessage(Address.fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true)
val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() val updateData = ClosedGroupUpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON()
val infoMessage = IncomingGroupMessage(m, groupID, updateData, true) val infoMessage = IncomingGroupMessage(m, groupID, updateData, true)
val smsDB = DatabaseFactory.getSmsDatabase(context) val smsDB = DatabaseFactory.getSmsDatabase(context)
smsDB.insertMessageInbox(infoMessage) smsDB.insertMessageInbox(infoMessage)
@ -416,7 +420,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
val userPublicKey = getUserPublicKey() val userPublicKey = getUserPublicKey()
val recipient = Recipient.from(context, Address.fromSerialized(groupID), false) val recipient = Recipient.from(context, Address.fromSerialized(groupID), false)
val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() ?: "" val updateData = ClosedGroupUpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() ?: ""
val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, groupID, null, sentTimestamp, 0, true, null, listOf(), listOf()) val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, groupID, null, sentTimestamp, 0, true, null, listOf(), listOf())
val mmsDB = DatabaseFactory.getMmsDatabase(context) val mmsDB = DatabaseFactory.getMmsDatabase(context)
val mmsSmsDB = DatabaseFactory.getMmsSmsDatabase(context) val mmsSmsDB = DatabaseFactory.getMmsSmsDatabase(context)

View File

@ -54,9 +54,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int lokiV20 = 41; private static final int lokiV20 = 41;
private static final int lokiV21 = 42; private static final int lokiV21 = 42;
private static final int lokiV22 = 43; private static final int lokiV22 = 43;
private static final int lokiV23 = 44;
// Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
private static final int DATABASE_VERSION = lokiV22; private static final int DATABASE_VERSION = lokiV23;
private static final String DATABASE_NAME = "signal.db"; private static final String DATABASE_NAME = "signal.db";
private final Context context; private final Context context;
@ -272,6 +273,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
"SendDeliveryReceiptJob"); "SendDeliveryReceiptJob");
} }
if (oldVersion < lokiV23) {
db.execSQL("ALTER TABLE groups ADD COLUMN zombie_members TEXT");
}
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();

View File

@ -23,7 +23,7 @@ import android.text.SpannableString;
import network.loki.messenger.R; import network.loki.messenger.R;
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact; import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase.Status; import org.thoughtcrime.securesms.database.SmsDatabase.Status;
import org.session.libsession.database.documents.IdentityKeyMismatch; import org.session.libsession.database.documents.IdentityKeyMismatch;

View File

@ -25,9 +25,9 @@ import android.text.style.StyleSpan;
import network.loki.messenger.R; import network.loki.messenger.R;
import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage; import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage;
import org.session.libsession.messaging.utilities.UpdateMessageBuilder; import org.session.libsession.messaging.utilities.ClosedGroupUpdateMessageBuilder;
import org.session.libsession.messaging.utilities.UpdateMessageData; import org.session.libsession.messaging.utilities.ClosedGroupUpdateMessageData;
import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase;
import org.session.libsession.database.documents.IdentityKeyMismatch; import org.session.libsession.database.documents.IdentityKeyMismatch;
@ -93,14 +93,14 @@ public abstract class MessageRecord extends DisplayRecord {
@Override @Override
public SpannableString getDisplayBody(@NonNull Context context) { public SpannableString getDisplayBody(@NonNull Context context) {
if(isGroupUpdateMessage()) { if(isGroupUpdateMessage()) {
UpdateMessageData updateMessageData = UpdateMessageData.Companion.fromJSON(getBody()); ClosedGroupUpdateMessageData updateMessageData = ClosedGroupUpdateMessageData.Companion.fromJSON(getBody());
return new SpannableString(UpdateMessageBuilder.INSTANCE.buildGroupUpdateMessage(context, updateMessageData, getIndividualRecipient().getAddress().serialize(), isOutgoing())); return new SpannableString(ClosedGroupUpdateMessageBuilder.INSTANCE.buildGroupUpdateMessage(context, updateMessageData, getIndividualRecipient().getAddress().serialize(), isOutgoing()));
} else if (isExpirationTimerUpdate()) { } else if (isExpirationTimerUpdate()) {
int seconds = (int) (getExpiresIn() / 1000); int seconds = (int) (getExpiresIn() / 1000);
return new SpannableString(UpdateMessageBuilder.INSTANCE.buildExpirationTimerMessage(context, seconds, getIndividualRecipient().getAddress().serialize(), isOutgoing())); return new SpannableString(ClosedGroupUpdateMessageBuilder.INSTANCE.buildExpirationTimerMessage(context, seconds, getIndividualRecipient().getAddress().serialize(), isOutgoing()));
} else if (isDataExtraction()) { } else if (isDataExtraction()) {
if (isScreenshotExtraction()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT, getIndividualRecipient().getAddress().serialize()))); if (isScreenshotExtraction()) return new SpannableString((ClosedGroupUpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT, getIndividualRecipient().getAddress().serialize())));
else if (isMediaSavedExtraction()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED, getIndividualRecipient().getAddress().serialize()))); else if (isMediaSavedExtraction()) return new SpannableString((ClosedGroupUpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED, getIndividualRecipient().getAddress().serialize())));
} }
// TODO below lines are left here for compatibility with older group update messages, it can be deleted later on // TODO below lines are left here for compatibility with older group update messages, it can be deleted later on
else if (isGroupUpdate() && isOutgoing()) { else if (isGroupUpdate() && isOutgoing()) {

View File

@ -5,7 +5,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact; import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.database.documents.IdentityKeyMismatch; import org.session.libsession.database.documents.IdentityKeyMismatch;

View File

@ -28,8 +28,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.utilities.UpdateMessageBuilder;
import org.session.libsession.messaging.utilities.UpdateMessageData;
import org.session.libsession.utilities.ExpirationUtil; import org.session.libsession.utilities.ExpirationUtil;
import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase;

View File

@ -4,6 +4,8 @@ import android.graphics.Bitmap;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.session.libsession.messaging.jobs.Data; import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.utilities.DownloadUtilities;
import org.session.libsignal.service.api.crypto.AttachmentCipherInputStream;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.session.libsession.messaging.threads.GroupRecord; import org.session.libsession.messaging.threads.GroupRecord;
@ -22,6 +24,7 @@ import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException; import org.session.libsignal.service.api.push.exceptions.NonSuccessfulResponseCodeException;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -92,7 +95,18 @@ public class AvatarDownloadJob extends BaseJob implements InjectableType {
attachment.deleteOnExit(); attachment.deleteOnExit();
SignalServiceAttachmentPointer pointer = new SignalServiceAttachmentPointer(avatarId, contentType, key, Optional.of(0), Optional.absent(), 0, 0, digest, fileName, false, Optional.absent(), url); SignalServiceAttachmentPointer pointer = new SignalServiceAttachmentPointer(avatarId, contentType, key, Optional.of(0), Optional.absent(), 0, 0, digest, fileName, false, Optional.absent(), url);
InputStream inputStream = receiver.retrieveAttachment(pointer, attachment, MAX_AVATAR_SIZE);
if (pointer.getUrl().isEmpty()) throw new InvalidMessageException("Missing attachment URL.");
DownloadUtilities.downloadFile(attachment, pointer.getUrl(), MAX_AVATAR_SIZE, null);
// Assume we're retrieving an attachment for an open group server if the digest is not set
InputStream inputStream;
if (!pointer.getDigest().isPresent()) {
inputStream = new FileInputStream(attachment);
} else {
inputStream = AttachmentCipherInputStream.createForAttachment(attachment, pointer.getSize().or(0), pointer.getKey(), pointer.getDigest().get());
}
Bitmap avatar = BitmapUtil.createScaledBitmap(context, new AttachmentModel(attachment, key, 0, digest), 500, 500); Bitmap avatar = BitmapUtil.createScaledBitmap(context, new AttachmentModel(attachment, key, 0, digest), 500, 500);
database.updateProfilePicture(groupId, avatar); database.updateProfilePicture(groupId, avatar);

View File

@ -10,9 +10,11 @@ import org.session.libsession.messaging.avatars.AvatarHelper;
import org.session.libsession.messaging.jobs.Data; import org.session.libsession.messaging.jobs.Data;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.utilities.DownloadUtilities;
import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util; import org.session.libsession.utilities.Util;
import org.session.libsignal.service.api.SignalServiceMessageReceiver; import org.session.libsignal.service.api.SignalServiceMessageReceiver;
import org.session.libsignal.service.api.crypto.ProfileCipherInputStream;
import org.session.libsignal.service.api.push.exceptions.PushNetworkException; import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
@ -22,6 +24,7 @@ import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -102,7 +105,8 @@ public class RetrieveProfileAvatarJob extends BaseJob implements InjectableType
File downloadDestination = File.createTempFile("avatar", ".jpg", context.getCacheDir()); File downloadDestination = File.createTempFile("avatar", ".jpg", context.getCacheDir());
try { try {
InputStream avatarStream = receiver.retrieveProfileAvatar(profileAvatar, downloadDestination, profileKey, MAX_PROFILE_SIZE_BYTES); DownloadUtilities.downloadFile(downloadDestination, profileAvatar, MAX_PROFILE_SIZE_BYTES, null);
InputStream avatarStream = new ProfileCipherInputStream(new FileInputStream(downloadDestination), profileKey);
File decryptDestination = File.createTempFile("avatar", ".jpg", context.getCacheDir()); File decryptDestination = File.createTempFile("avatar", ".jpg", context.getCacheDir());
Util.copy(avatarStream, new FileOutputStream(decryptDestination)); Util.copy(avatarStream, new FileOutputStream(decryptDestination));

View File

@ -25,7 +25,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil.OpenGraph;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.sending_receiving.attachments.UriAttachment; import org.session.libsession.messaging.sending_receiving.attachments.UriAttachment;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
import org.session.libsession.utilities.concurrent.SignalExecutors; import org.session.libsession.utilities.concurrent.SignalExecutors;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;

View File

@ -13,9 +13,8 @@ import org.session.libsession.utilities.Debouncer;
import org.session.libsession.utilities.Util; import org.session.libsession.utilities.Util;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
import java.util.Collections;
import java.util.List; import java.util.List;

View File

@ -37,8 +37,14 @@ import java.io.IOException
class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
private val originalMembers = HashSet<String>() private val originalMembers = HashSet<String>()
private val zombies = HashSet<String>()
private val members = HashSet<String>() private val members = HashSet<String>()
private val allMembers: Set<String>
get() {
return members + zombies
}
private var hasNameChanged = false private var hasNameChanged = false
private var isSelfAdmin = false
private var isLoading = false private var isLoading = false
set(newValue) { field = newValue; invalidateOptionsMenu() } set(newValue) { field = newValue; invalidateOptionsMenu() }
@ -54,7 +60,10 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
} }
private val memberListAdapter by lazy { private val memberListAdapter by lazy {
EditClosedGroupMembersAdapter(this, GlideApp.with(this), this::onMemberClick) if (isSelfAdmin)
EditClosedGroupMembersAdapter(this, GlideApp.with(this), isSelfAdmin, this::onMemberClick)
else
EditClosedGroupMembersAdapter(this, GlideApp.with(this), isSelfAdmin)
} }
private lateinit var mainContentContainer: LinearLayout private lateinit var mainContentContainer: LinearLayout
@ -81,7 +90,10 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
ThemeUtil.getThemedDrawableResId(this, R.attr.actionModeCloseDrawable)) ThemeUtil.getThemedDrawableResId(this, R.attr.actionModeCloseDrawable))
groupID = intent.getStringExtra(groupIDKey)!! groupID = intent.getStringExtra(groupIDKey)!!
originalName = DatabaseFactory.getGroupDatabase(this).getGroup(groupID).get().title val groupInfo = DatabaseFactory.getGroupDatabase(this).getGroup(groupID).get()
originalName = groupInfo.title
isSelfAdmin = groupInfo.admins.any{ it.serialize() == TextSecurePreferences.getLocalNumber(this) }
name = originalName name = originalName
mainContentContainer = findViewById(R.id.mainContentContainer) mainContentContainer = findViewById(R.id.mainContentContainer)
@ -116,31 +128,35 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
} }
} }
LoaderManager.getInstance(this).initLoader(loaderID, null, object : LoaderManager.LoaderCallbacks<List<String>> { LoaderManager.getInstance(this).initLoader(loaderID, null, object : LoaderManager.LoaderCallbacks<GroupMembers> {
override fun onCreateLoader(id: Int, bundle: Bundle?): Loader<List<String>> { override fun onCreateLoader(id: Int, bundle: Bundle?): Loader<GroupMembers> {
return EditClosedGroupLoader(this@EditClosedGroupActivity, groupID) return EditClosedGroupLoader(this@EditClosedGroupActivity, groupID)
} }
override fun onLoadFinished(loader: Loader<List<String>>, members: List<String>) { override fun onLoadFinished(loader: Loader<GroupMembers>, groupMembers: GroupMembers) {
// We no longer need any subsequent loading events // We no longer need any subsequent loading events
// (they will occur on every activity resume). // (they will occur on every activity resume).
LoaderManager.getInstance(this@EditClosedGroupActivity).destroyLoader(loaderID) LoaderManager.getInstance(this@EditClosedGroupActivity).destroyLoader(loaderID)
members.clear()
members.addAll(groupMembers.members.toHashSet())
zombies.clear()
zombies.addAll(groupMembers.zombieMembers.toHashSet())
originalMembers.clear() originalMembers.clear()
originalMembers.addAll(members.toHashSet()) originalMembers.addAll(members + zombies)
updateMembers(originalMembers) updateMembers()
} }
override fun onLoaderReset(loader: Loader<List<String>>) { override fun onLoaderReset(loader: Loader<GroupMembers>) {
updateMembers(setOf()) updateMembers()
} }
}) })
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_edit_closed_group, menu) menuInflater.inflate(R.menu.menu_edit_closed_group, menu)
return members.isNotEmpty() && !isLoading return allMembers.isNotEmpty() && !isLoading
} }
// endregion // endregion
@ -153,8 +169,8 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
if (data == null || data.extras == null || !data.hasExtra(SelectContactsActivity.selectedContactsKey)) return if (data == null || data.extras == null || !data.hasExtra(SelectContactsActivity.selectedContactsKey)) return
val selectedContacts = data.extras!!.getStringArray(SelectContactsActivity.selectedContactsKey)!!.toSet() val selectedContacts = data.extras!!.getStringArray(SelectContactsActivity.selectedContactsKey)!!.toSet()
val changedMembers = members + selectedContacts members.addAll(selectedContacts)
updateMembers(changedMembers) updateMembers()
} }
} }
} }
@ -173,17 +189,12 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
} }
} }
private fun updateMembers(members: Set<String>) { private fun updateMembers() {
this.members.clear() memberListAdapter.setMembers(allMembers)
this.members.addAll(members) memberListAdapter.setZombieMembers(zombies)
memberListAdapter.setMembers(members)
val admins = DatabaseFactory.getGroupDatabase(this).getGroup(groupID).get().admins.map { it.toString() }.toMutableSet() mainContentContainer.visibility = if (allMembers.isEmpty()) View.GONE else View.VISIBLE
admins.remove(TextSecurePreferences.getLocalNumber(this)) emptyStateContainer.visibility = if (allMembers.isEmpty()) View.VISIBLE else View.GONE
memberListAdapter.setLockedMembers(admins)
mainContentContainer.visibility = if (members.isEmpty()) View.GONE else View.VISIBLE
emptyStateContainer.visibility = if (members.isEmpty()) View.VISIBLE else View.GONE
invalidateOptionsMenu() invalidateOptionsMenu()
} }
@ -200,8 +211,9 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
private fun onMemberClick(member: String) { private fun onMemberClick(member: String) {
val bottomSheet = ClosedGroupEditingOptionsBottomSheet() val bottomSheet = ClosedGroupEditingOptionsBottomSheet()
bottomSheet.onRemoveTapped = { bottomSheet.onRemoveTapped = {
val changedMembers = members - member if (zombies.contains(member)) zombies.remove(member)
updateMembers(changedMembers) else members.remove(member)
updateMembers()
bottomSheet.dismiss() bottomSheet.dismiss()
} }
bottomSheet.show(supportFragmentManager, "GroupEditingOptionsBottomSheet") bottomSheet.show(supportFragmentManager, "GroupEditingOptionsBottomSheet")
@ -209,7 +221,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
private fun onAddMembersClick() { private fun onAddMembersClick() {
val intent = Intent(this@EditClosedGroupActivity, SelectContactsActivity::class.java) val intent = Intent(this@EditClosedGroupActivity, SelectContactsActivity::class.java)
intent.putExtra(SelectContactsActivity.usersToExcludeKey, members.toTypedArray()) intent.putExtra(SelectContactsActivity.usersToExcludeKey, allMembers.toTypedArray())
intent.putExtra(SelectContactsActivity.emptyStateTextKey, "No contacts to add") intent.putExtra(SelectContactsActivity.emptyStateTextKey, "No contacts to add")
startActivityForResult(intent, addUsersRequestCode) startActivityForResult(intent, addUsersRequestCode)
} }
@ -229,7 +241,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
} }
private fun commitChanges() { private fun commitChanges() {
val hasMemberListChanges = (members != originalMembers) val hasMemberListChanges = (allMembers != originalMembers)
if (!hasNameChanged && !hasMemberListChanges) { if (!hasNameChanged && !hasMemberListChanges) {
return finish() return finish()
@ -237,15 +249,13 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
val name = if (hasNameChanged) this.name else originalName val name = if (hasNameChanged) this.name else originalName
val members = this.members.map { val members = this.allMembers.map {
Recipient.from(this, Address.fromSerialized(it), false) Recipient.from(this, Address.fromSerialized(it), false)
}.toSet() }.toSet()
val originalMembers = this.originalMembers.map { val originalMembers = this.originalMembers.map {
Recipient.from(this, Address.fromSerialized(it), false) Recipient.from(this, Address.fromSerialized(it), false)
}.toSet() }.toSet()
val admins = members.toSet() //TODO For now, consider all the users to be admins.
var isClosedGroup: Boolean var isClosedGroup: Boolean
var groupPublicKey: String? var groupPublicKey: String?
try { try {
@ -303,4 +313,6 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
} }
} }
} }
class GroupMembers(val members: List<String>, val zombieMembers: List<String>) { }
} }

View File

@ -4,12 +4,19 @@ import android.content.Context
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.util.AsyncLoader import org.thoughtcrime.securesms.util.AsyncLoader
class EditClosedGroupLoader(context: Context, val groupID: String) : AsyncLoader<List<String>>(context) { class EditClosedGroupLoader(context: Context, val groupID: String) : AsyncLoader<EditClosedGroupActivity.GroupMembers>(context) {
override fun loadInBackground(): List<String> { override fun loadInBackground(): EditClosedGroupActivity.GroupMembers {
val members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupID, true) val groupDatabase = DatabaseFactory.getGroupDatabase(context)
return members.map { val members = groupDatabase.getGroupMembers(groupID, true)
val zombieMembers = groupDatabase.getGroupZombieMembers(groupID)
return EditClosedGroupActivity.GroupMembers(
members.map {
it.address.toString()
},
zombieMembers.map {
it.address.toString() it.address.toString()
} }
)
} }
} }

View File

@ -7,15 +7,17 @@ import org.session.libsession.messaging.threads.Address
import org.thoughtcrime.securesms.loki.views.UserView import org.thoughtcrime.securesms.loki.views.UserView
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.messaging.threads.recipients.Recipient
import org.session.libsession.utilities.TextSecurePreferences
class EditClosedGroupMembersAdapter( class EditClosedGroupMembersAdapter(
private val context: Context, private val context: Context,
private val glide: GlideRequests, private val glide: GlideRequests,
private val admin: Boolean,
private val memberClickListener: ((String) -> Unit)? = null private val memberClickListener: ((String) -> Unit)? = null
) : RecyclerView.Adapter<EditClosedGroupMembersAdapter.ViewHolder>() { ) : RecyclerView.Adapter<EditClosedGroupMembersAdapter.ViewHolder>() {
private val members = ArrayList<String>() private val members = ArrayList<String>()
private val lockedMembers = HashSet<String>() private val zombieMembers = ArrayList<String>()
fun setMembers(members: Collection<String>) { fun setMembers(members: Collection<String>) {
this.members.clear() this.members.clear()
@ -23,9 +25,9 @@ class EditClosedGroupMembersAdapter(
notifyDataSetChanged() notifyDataSetChanged()
} }
fun setLockedMembers(members: Collection<String>) { fun setZombieMembers(members: Collection<String>) {
this.lockedMembers.clear() this.zombieMembers.clear()
this.lockedMembers.addAll(members) this.zombieMembers.addAll(members)
notifyDataSetChanged() notifyDataSetChanged()
} }
@ -39,15 +41,20 @@ class EditClosedGroupMembersAdapter(
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
val member = members[position] val member = members[position]
val lockedMember = lockedMembers.contains(member) val unlocked = admin && member != TextSecurePreferences.getLocalNumber(context)
viewHolder.view.bind(Recipient.from( viewHolder.view.bind(Recipient.from(
context, context,
Address.fromSerialized(member), false), Address.fromSerialized(member), false),
glide, glide,
if (lockedMember) UserView.ActionIndicator.None else UserView.ActionIndicator.Menu) if (unlocked) UserView.ActionIndicator.Menu else UserView.ActionIndicator.None)
if (!lockedMember) { if (zombieMembers.contains(member))
viewHolder.view.alpha = 0.5F
else
viewHolder.view.alpha = 1F
if (unlocked) {
viewHolder.view.setOnClickListener { this.memberClickListener?.invoke(member) } viewHolder.view.setOnClickListener { this.memberClickListener?.invoke(member) }
} }
} }

View File

@ -30,10 +30,10 @@ import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.opengroups.OpenGroupAPI import org.session.libsession.messaging.mentions.MentionsManager
import org.session.libsession.messaging.open_groups.OpenGroupAPI
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.utilities.* import org.session.libsession.utilities.*
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager
import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.service.loki.utilities.toHexString
import org.session.libsignal.utilities.ThreadUtils import org.session.libsignal.utilities.ThreadUtils
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
@ -139,7 +139,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
val userDB = DatabaseFactory.getLokiUserDatabase(this) val userDB = DatabaseFactory.getLokiUserDatabase(this)
val userPublicKey = TextSecurePreferences.getLocalNumber(this) val userPublicKey = TextSecurePreferences.getLocalNumber(this)
if (userPublicKey != null) { if (userPublicKey != null) {
MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB) MentionsManager.configureIfNeeded(userPublicKey, userDB)
application.publicChatManager.startPollersIfNeeded() application.publicChatManager.startPollersIfNeeded()
JobQueue.shared.resumePendingJobs() JobQueue.shared.resumePendingJobs()
} }

View File

@ -27,7 +27,7 @@ import kotlinx.coroutines.launch
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.libsignal.util.KeyHelper import org.session.libsignal.libsignal.util.KeyHelper
import org.session.libsignal.service.loki.crypto.MnemonicCodec import org.session.libsignal.service.loki.MnemonicCodec
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
@ -45,7 +45,7 @@ class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDel
private var restoreJob: Job? = null private var restoreJob: Job? = null
override fun onBackPressed() { override fun onBackPressed() {
if (restoreJob?.isActive == true) return // don't allow going back with pending job if (restoreJob?.isActive == true) return // Don't allow going back with a pending job
super.onBackPressed() super.onBackPressed()
} }
@ -53,14 +53,12 @@ class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDel
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setUpActionBarSessionLogo() setUpActionBarSessionLogo()
// Set the registration sync variables
TextSecurePreferences.apply { TextSecurePreferences.apply {
setHasViewedSeed(this@LinkDeviceActivity, true) setHasViewedSeed(this@LinkDeviceActivity, true)
setConfigurationMessageSynced(this@LinkDeviceActivity, false) setConfigurationMessageSynced(this@LinkDeviceActivity, false)
setRestorationTime(this@LinkDeviceActivity, System.currentTimeMillis()) setRestorationTime(this@LinkDeviceActivity, System.currentTimeMillis())
setLastProfileUpdateTime(this@LinkDeviceActivity, 0) setLastProfileUpdateTime(this@LinkDeviceActivity, 0)
} }
// registration variables are synced
setContentView(R.layout.activity_link_device) setContentView(R.layout.activity_link_device)
viewPager.adapter = adapter viewPager.adapter = adapter
tabLayout.setupWithViewPager(viewPager) tabLayout.setupWithViewPager(viewPager)

View File

@ -19,12 +19,12 @@ import android.widget.Toast
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import kotlinx.android.synthetic.main.activity_path.* import kotlinx.android.synthetic.main.activity_path.*
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.snode.OnionRequestAPI
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.loki.utilities.* import org.thoughtcrime.securesms.loki.utilities.*
import org.thoughtcrime.securesms.loki.views.GlowViewUtilities import org.thoughtcrime.securesms.loki.views.GlowViewUtilities
import org.thoughtcrime.securesms.loki.views.PathDotView import org.thoughtcrime.securesms.loki.views.PathDotView
import org.session.libsignal.service.loki.api.Snode import org.session.libsignal.service.loki.Snode
import org.session.libsignal.service.loki.api.onionrequests.OnionRequestAPI
class PathActivity : PassphraseRequiredActionBarActivity() { class PathActivity : PassphraseRequiredActionBarActivity() {
private val broadcastReceivers = mutableListOf<BroadcastReceiver>() private val broadcastReceivers = mutableListOf<BroadcastReceiver>()

View File

@ -15,7 +15,7 @@ import kotlinx.android.synthetic.main.activity_recovery_phrase_restore.*
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.libsignal.util.KeyHelper import org.session.libsignal.libsignal.util.KeyHelper
import org.session.libsignal.service.loki.crypto.MnemonicCodec import org.session.libsignal.service.loki.MnemonicCodec
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Hex
import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.BaseActionBarActivity
@ -30,14 +30,12 @@ class RecoveryPhraseRestoreActivity : BaseActionBarActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setUpActionBarSessionLogo() setUpActionBarSessionLogo()
// Set the registration sync variables
TextSecurePreferences.apply { TextSecurePreferences.apply {
setHasViewedSeed(this@RecoveryPhraseRestoreActivity, true) setHasViewedSeed(this@RecoveryPhraseRestoreActivity, true)
setConfigurationMessageSynced(this@RecoveryPhraseRestoreActivity, false) setConfigurationMessageSynced(this@RecoveryPhraseRestoreActivity, false)
setRestorationTime(this@RecoveryPhraseRestoreActivity, System.currentTimeMillis()) setRestorationTime(this@RecoveryPhraseRestoreActivity, System.currentTimeMillis())
setLastProfileUpdateTime(this@RecoveryPhraseRestoreActivity, System.currentTimeMillis()) setLastProfileUpdateTime(this@RecoveryPhraseRestoreActivity, System.currentTimeMillis())
} }
// registration variables are synced
setContentView(R.layout.activity_recovery_phrase_restore) setContentView(R.layout.activity_recovery_phrase_restore)
mnemonicEditText.imeOptions = mnemonicEditText.imeOptions or 16777216 // Always use incognito keyboard mnemonicEditText.imeOptions = mnemonicEditText.imeOptions or 16777216 // Always use incognito keyboard
restoreButton.setOnClickListener { restore() } restoreButton.setOnClickListener { restore() }

View File

@ -39,14 +39,12 @@ class RegisterActivity : BaseActionBarActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_register) setContentView(R.layout.activity_register)
setUpActionBarSessionLogo() setUpActionBarSessionLogo()
// Set the registration sync variables
TextSecurePreferences.apply { TextSecurePreferences.apply {
setHasViewedSeed(this@RegisterActivity, false) setHasViewedSeed(this@RegisterActivity, false)
setConfigurationMessageSynced(this@RegisterActivity, true) setConfigurationMessageSynced(this@RegisterActivity, true)
setRestorationTime(this@RegisterActivity, 0) setRestorationTime(this@RegisterActivity, 0)
setLastProfileUpdateTime(this@RegisterActivity, System.currentTimeMillis()) setLastProfileUpdateTime(this@RegisterActivity, System.currentTimeMillis())
} }
// registration variables are synced
registerButton.setOnClickListener { register() } registerButton.setOnClickListener { register() }
copyButton.setOnClickListener { copyPublicKey() } copyButton.setOnClickListener { copyPublicKey() }
val termsExplanation = SpannableStringBuilder("By using this service, you agree to our Terms of Service and Privacy Policy") val termsExplanation = SpannableStringBuilder("By using this service, you agree to our Terms of Service and Privacy Policy")

View File

@ -16,7 +16,7 @@ import org.session.libsession.utilities.IdentityKeyUtil
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
import org.thoughtcrime.securesms.loki.utilities.getColorWithID import org.thoughtcrime.securesms.loki.utilities.getColorWithID
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.service.loki.crypto.MnemonicCodec import org.session.libsignal.service.loki.MnemonicCodec
import org.session.libsignal.service.loki.utilities.hexEncodedPrivateKey import org.session.libsignal.service.loki.utilities.hexEncodedPrivateKey
class SeedActivity : BaseActionBarActivity() { class SeedActivity : BaseActionBarActivity() {

View File

@ -28,13 +28,13 @@ import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.task import nl.komponents.kovenant.task
import nl.komponents.kovenant.ui.alwaysUi import nl.komponents.kovenant.ui.alwaysUi
import org.session.libsession.messaging.avatars.AvatarHelper import org.session.libsession.messaging.avatars.AvatarHelper
import org.session.libsession.messaging.opengroups.OpenGroupAPI import org.session.libsession.messaging.file_server.FileServerAPI
import org.session.libsession.messaging.open_groups.OpenGroupAPI
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.preferences.ProfileKeyUtil import org.session.libsession.utilities.preferences.ProfileKeyUtil
import org.session.libsignal.service.api.util.StreamDetails import org.session.libsignal.service.api.util.StreamDetails
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.avatar.AvatarSelection import org.thoughtcrime.securesms.avatar.AvatarSelection

View File

@ -8,13 +8,14 @@ import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.all import nl.komponents.kovenant.all
import nl.komponents.kovenant.functional.map import nl.komponents.kovenant.functional.map
import org.session.libsession.messaging.jobs.MessageReceiveJob import org.session.libsession.messaging.jobs.MessageReceiveJob
import org.session.libsession.messaging.opengroups.OpenGroup import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPoller
import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.service.loki.api.SnodeAPI
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import java.io.IOException
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Worker(context, params) { class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Worker(context, params) {
@ -70,7 +71,7 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
// Private chats // Private chats
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
val privateChatsPromise = SnodeAPI.shared.getMessages(userPublicKey).map { envelopes -> val privateChatsPromise = SnodeAPI.getMessages(userPublicKey).map { envelopes ->
envelopes.map { envelope -> envelopes.map { envelope ->
MessageReceiveJob(envelope.toByteArray(), false).executeAsync() MessageReceiveJob(envelope.toByteArray(), false).executeAsync()
} }
@ -78,7 +79,7 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
promises.addAll(privateChatsPromise.get()) promises.addAll(privateChatsPromise.get())
// Closed groups // Closed groups
promises.addAll(ApplicationContext.getInstance(context).closedGroupPoller.pollOnce()) promises.addAll(ClosedGroupPoller().pollOnce())
// Open Groups // Open Groups
val openGroups = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().map { (_,chat)-> val openGroups = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().map { (_,chat)->

View File

@ -3,12 +3,12 @@ package org.thoughtcrime.securesms.loki.api
import android.content.Context import android.content.Context
import nl.komponents.kovenant.functional.map import nl.komponents.kovenant.functional.map
import okhttp3.* import okhttp3.*
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI
import org.session.libsession.snode.OnionRequestAPI
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.service.loki.api.PushNotificationAPI
import org.session.libsignal.service.loki.api.onionrequests.OnionRequestAPI
import org.session.libsignal.service.loki.utilities.retryIfNeeded import org.session.libsignal.service.loki.utilities.retryIfNeeded
object LokiPushNotificationManager { object LokiPushNotificationManager {
@ -16,10 +16,10 @@ object LokiPushNotificationManager {
private val tokenExpirationInterval = 12 * 60 * 60 * 1000 private val tokenExpirationInterval = 12 * 60 * 60 * 1000
private val server by lazy { private val server by lazy {
PushNotificationAPI.shared.server PushNotificationAPI.server
} }
private val pnServerPublicKey by lazy { private val pnServerPublicKey by lazy {
PushNotificationAPI.pnServerPublicKey PushNotificationAPI.serverPublicKey
} }
enum class ClosedGroupOperation { enum class ClosedGroupOperation {

View File

@ -2,9 +2,9 @@ package org.thoughtcrime.securesms.loki.api
import android.content.Context import android.content.Context
import androidx.work.* import androidx.work.*
import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
import org.session.libsignal.service.loki.api.opengroups.PublicChat
/** /**
* Delegates the [OpenGroupUtilities.updateGroupInfo] call to the work manager. * Delegates the [OpenGroupUtilities.updateGroupInfo] call to the work manager.
@ -40,7 +40,7 @@ class PublicChatInfoUpdateWorker(val context: Context, params: WorkerParameters)
val serverUrl = inputData.getString(DATA_KEY_SERVER_URL)!! val serverUrl = inputData.getString(DATA_KEY_SERVER_URL)!!
val channel = inputData.getLong(DATA_KEY_CHANNEL, -1) val channel = inputData.getLong(DATA_KEY_CHANNEL, -1)
val publicChatId = PublicChat.getId(channel, serverUrl) val publicChatId = OpenGroup.getId(channel, serverUrl)
return try { return try {
Log.v(TAG, "Updating open group info for $publicChatId.") Log.v(TAG, "Updating open group info for $publicChatId.")

View File

@ -5,14 +5,13 @@ import android.database.ContentObserver
import android.graphics.Bitmap import android.graphics.Bitmap
import android.text.TextUtils import android.text.TextUtils
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.opengroups.OpenGroup import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.messaging.opengroups.OpenGroupAPI import org.session.libsession.messaging.open_groups.OpenGroupAPI
import org.session.libsession.messaging.opengroups.OpenGroupInfo import org.session.libsession.messaging.open_groups.OpenGroupInfo
import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.Util import org.session.libsession.utilities.Util
import org.session.libsignal.service.loki.api.opengroups.PublicChat
import org.thoughtcrime.securesms.database.DatabaseContentProviders import org.thoughtcrime.securesms.database.DatabaseContentProviders
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.groups.GroupManager
@ -75,7 +74,7 @@ class PublicChatManager(private val context: Context) {
@WorkerThread @WorkerThread
public fun addChat(server: String, channel: Long, info: OpenGroupInfo): OpenGroup { public fun addChat(server: String, channel: Long, info: OpenGroupInfo): OpenGroup {
val chat = PublicChat(channel, server, info.displayName, true) val chat = OpenGroup(channel, server, info.displayName, true)
var threadID = GroupManager.getOpenGroupThreadID(chat.id, context) var threadID = GroupManager.getOpenGroupThreadID(chat.id, context)
var profilePicture: Bitmap? = null var profilePicture: Bitmap? = null
// Create the group if we don't have one // Create the group if we don't have one
@ -96,12 +95,12 @@ class PublicChatManager(private val context: Context) {
// Start polling // Start polling
Util.runOnMain { startPollersIfNeeded() } Util.runOnMain { startPollersIfNeeded() }
return OpenGroup.from(chat) return chat
} }
public fun removeChat(server: String, channel: Long) { public fun removeChat(server: String, channel: Long) {
val threadDB = DatabaseFactory.getThreadDatabase(context) val threadDB = DatabaseFactory.getThreadDatabase(context)
val groupId = PublicChat.getId(channel, server) val groupId = OpenGroup.getId(channel, server)
val threadId = GroupManager.getOpenGroupThreadID(groupId, context) val threadId = GroupManager.getOpenGroupThreadID(groupId, context)
val groupAddress = threadDB.getRecipientForThreadId(threadId)!!.address.serialize() val groupAddress = threadDB.getRecipientForThreadId(threadId)!!.address.serialize()
GroupManager.deleteGroup(groupAddress, context) GroupManager.deleteGroup(groupAddress, context)
@ -110,7 +109,7 @@ class PublicChatManager(private val context: Context) {
} }
private fun refreshChatsAndPollers() { private fun refreshChatsAndPollers() {
val storage = MessagingConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val chatsInDB = storage.getAllOpenGroups() val chatsInDB = storage.getAllOpenGroups()
val removedChatThreadIds = chats.keys.filter { !chatsInDB.keys.contains(it) } val removedChatThreadIds = chats.keys.filter { !chatsInDB.keys.contains(it) }
removedChatThreadIds.forEach { pollers.remove(it)?.stop() } removedChatThreadIds.forEach { pollers.remove(it)?.stop() }

View File

@ -6,8 +6,8 @@ import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage import com.google.firebase.messaging.RemoteMessage
import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.jobs.MessageReceiveJob import org.session.libsession.messaging.jobs.MessageReceiveJob
import org.session.libsession.messaging.utilities.MessageWrapper
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.service.loki.api.MessageWrapper
import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
import org.thoughtcrime.securesms.notifications.NotificationChannels import org.thoughtcrime.securesms.notifications.NotificationChannels

View File

@ -7,8 +7,8 @@ import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.libsignal.ecc.DjbECPrivateKey import org.session.libsignal.libsignal.ecc.DjbECPrivateKey
import org.session.libsignal.libsignal.ecc.DjbECPublicKey import org.session.libsignal.libsignal.ecc.DjbECPublicKey
import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.ecc.ECKeyPair
import org.session.libsignal.service.loki.api.Snode import org.session.libsignal.service.loki.Snode
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol import org.session.libsignal.service.loki.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.utilities.PublicKeyValidation import org.session.libsignal.service.loki.utilities.PublicKeyValidation
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.service.loki.utilities.toHexString

View File

@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.loki.database
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import org.session.libsession.messaging.threads.Address
import org.thoughtcrime.securesms.database.Database import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
@ -10,7 +9,7 @@ import org.thoughtcrime.securesms.loki.utilities.get
import org.thoughtcrime.securesms.loki.utilities.getInt import org.thoughtcrime.securesms.loki.utilities.getInt
import org.thoughtcrime.securesms.loki.utilities.getString import org.thoughtcrime.securesms.loki.utilities.getString
import org.thoughtcrime.securesms.loki.utilities.insertOrUpdate import org.thoughtcrime.securesms.loki.utilities.insertOrUpdate
import org.session.libsignal.service.loki.database.LokiMessageDatabaseProtocol import org.session.libsignal.service.loki.LokiMessageDatabaseProtocol
class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiMessageDatabaseProtocol { class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiMessageDatabaseProtocol {

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.loki.database
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.database.Cursor import android.database.Cursor
import org.session.libsession.messaging.open_groups.OpenGroup
import org.thoughtcrime.securesms.database.Database import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
@ -12,13 +13,11 @@ import org.thoughtcrime.securesms.loki.utilities.*
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.messaging.threads.recipients.Recipient
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.service.loki.api.opengroups.PublicChat
import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.service.loki.database.LokiThreadDatabaseProtocol
import org.session.libsignal.service.loki.utilities.PublicKeyValidation import org.session.libsignal.service.loki.utilities.PublicKeyValidation
class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiThreadDatabaseProtocol { class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) {
companion object { companion object {
private val sessionResetTable = "loki_thread_session_reset_database" private val sessionResetTable = "loki_thread_session_reset_database"
@ -31,22 +30,22 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
@JvmStatic val createPublicChatTableCommand = "CREATE TABLE $publicChatTable ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);" @JvmStatic val createPublicChatTableCommand = "CREATE TABLE $publicChatTable ($threadID INTEGER PRIMARY KEY, $publicChat TEXT);"
} }
override fun getThreadID(hexEncodedPublicKey: String): Long { fun getThreadID(hexEncodedPublicKey: String): Long {
val address = Address.fromSerialized(hexEncodedPublicKey) val address = Address.fromSerialized(hexEncodedPublicKey)
val recipient = Recipient.from(context, address, false) val recipient = Recipient.from(context, address, false)
return DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient) return DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient)
} }
fun getAllPublicChats(): Map<Long, PublicChat> { fun getAllPublicChats(): Map<Long, OpenGroup> {
val database = databaseHelper.readableDatabase val database = databaseHelper.readableDatabase
var cursor: Cursor? = null var cursor: Cursor? = null
val result = mutableMapOf<Long, PublicChat>() val result = mutableMapOf<Long, OpenGroup>()
try { try {
cursor = database.rawQuery("select * from $publicChatTable", null) cursor = database.rawQuery("select * from $publicChatTable", null)
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
val threadID = cursor.getLong(threadID) val threadID = cursor.getLong(threadID)
val string = cursor.getString(publicChat) val string = cursor.getString(publicChat)
val publicChat = PublicChat.fromJSON(string) val publicChat = OpenGroup.fromJSON(string)
if (publicChat != null) { result[threadID] = publicChat } if (publicChat != null) { result[threadID] = publicChat }
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -61,16 +60,16 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
return getAllPublicChats().values.fold(setOf()) { set, chat -> set.plus(chat.server) } return getAllPublicChats().values.fold(setOf()) { set, chat -> set.plus(chat.server) }
} }
override fun getPublicChat(threadID: Long): PublicChat? { fun getPublicChat(threadID: Long): OpenGroup? {
if (threadID < 0) { return null } if (threadID < 0) { return null }
val database = databaseHelper.readableDatabase val database = databaseHelper.readableDatabase
return database.get(publicChatTable, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) { cursor -> return database.get(publicChatTable, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) { cursor ->
val publicChatAsJSON = cursor.getString(publicChat) val publicChatAsJSON = cursor.getString(publicChat)
PublicChat.fromJSON(publicChatAsJSON) OpenGroup.fromJSON(publicChatAsJSON)
} }
} }
override fun setPublicChat(publicChat: PublicChat, threadID: Long) { fun setPublicChat(publicChat: OpenGroup, threadID: Long) {
if (threadID < 0) { return } if (threadID < 0) { return }
val database = databaseHelper.writableDatabase val database = databaseHelper.writableDatabase
val contentValues = ContentValues(2) val contentValues = ContentValues(2)
@ -79,7 +78,7 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
database.insertOrUpdate(publicChatTable, contentValues, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) database.insertOrUpdate(publicChatTable, contentValues, "${Companion.threadID} = ?", arrayOf( threadID.toString() ))
} }
override fun removePublicChat(threadID: Long) { fun removePublicChat(threadID: Long) {
databaseHelper.writableDatabase.delete(publicChatTable, "${Companion.threadID} = ?", arrayOf( threadID.toString() )) databaseHelper.writableDatabase.delete(publicChatTable, "${Companion.threadID} = ?", arrayOf( threadID.toString() ))
} }
} }

View File

@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.loki.utilities.get
import org.thoughtcrime.securesms.loki.utilities.insertOrUpdate import org.thoughtcrime.securesms.loki.utilities.insertOrUpdate
import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.messaging.threads.recipients.Recipient
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol import org.session.libsignal.service.loki.LokiUserDatabaseProtocol
class LokiUserDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiUserDatabaseProtocol { class LokiUserDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiUserDatabaseProtocol {

View File

@ -15,7 +15,7 @@ import kotlinx.android.synthetic.main.dialog_seed.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.utilities.IdentityKeyUtil import org.session.libsession.utilities.IdentityKeyUtil
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
import org.session.libsignal.service.loki.crypto.MnemonicCodec import org.session.libsignal.service.loki.MnemonicCodec
import org.session.libsignal.service.loki.utilities.hexEncodedPrivateKey import org.session.libsignal.service.loki.utilities.hexEncodedPrivateKey

View File

@ -31,6 +31,7 @@ import org.session.libsession.utilities.TextSecurePreferences
import java.util.* import java.util.*
object ClosedGroupsProtocolV2 { object ClosedGroupsProtocolV2 {
@JvmStatic @JvmStatic
fun handleMessage(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { fun handleMessage(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
if (!isValid(context, closedGroupUpdate, senderPublicKey, sentTimestamp)) { return } if (!isValid(context, closedGroupUpdate, senderPublicKey, sentTimestamp)) { return }
@ -40,7 +41,6 @@ object ClosedGroupsProtocolV2 {
DataMessage.ClosedGroupControlMessage.Type.MEMBERS_ADDED -> handleClosedGroupMembersAdded(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey) DataMessage.ClosedGroupControlMessage.Type.MEMBERS_ADDED -> handleClosedGroupMembersAdded(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
DataMessage.ClosedGroupControlMessage.Type.NAME_CHANGE -> handleClosedGroupNameChange(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey) DataMessage.ClosedGroupControlMessage.Type.NAME_CHANGE -> handleClosedGroupNameChange(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> handleClosedGroupMemberLeft(context, sentTimestamp, groupPublicKey, senderPublicKey) DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> handleClosedGroupMemberLeft(context, sentTimestamp, groupPublicKey, senderPublicKey)
DataMessage.ClosedGroupControlMessage.Type.UPDATE -> handleClosedGroupUpdate(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR -> handleGroupEncryptionKeyPair(context, closedGroupUpdate, groupPublicKey, senderPublicKey) DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR -> handleGroupEncryptionKeyPair(context, closedGroupUpdate, groupPublicKey, senderPublicKey)
else -> { else -> {
Log.d("Loki","Can't handle closed group update of unknown type: ${closedGroupUpdate.type}") Log.d("Loki","Can't handle closed group update of unknown type: ${closedGroupUpdate.type}")
@ -64,7 +64,6 @@ object ClosedGroupsProtocolV2 {
DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> { DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> {
senderPublicKey.isNotEmpty() senderPublicKey.isNotEmpty()
} }
DataMessage.ClosedGroupControlMessage.Type.UPDATE,
DataMessage.ClosedGroupControlMessage.Type.NAME_CHANGE -> { DataMessage.ClosedGroupControlMessage.Type.NAME_CHANGE -> {
!closedGroupUpdate.name.isNullOrEmpty() !closedGroupUpdate.name.isNullOrEmpty()
} }

View File

@ -2,24 +2,13 @@ package org.thoughtcrime.securesms.loki.protocol
import android.content.Context import android.content.Context
import com.google.protobuf.ByteString import com.google.protobuf.ByteString
import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.ConfigurationMessage
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.threads.recipients.Recipient
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.preferences.ProfileKeyUtil
import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.Hex
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
import org.thoughtcrime.securesms.loki.utilities.ContactUtilities import org.thoughtcrime.securesms.loki.utilities.ContactUtilities
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
object MultiDeviceProtocol { object MultiDeviceProtocol {
@ -51,80 +40,4 @@ object MultiDeviceProtocol {
TextSecurePreferences.setLastConfigurationSyncTime(context, System.currentTimeMillis()) TextSecurePreferences.setLastConfigurationSyncTime(context, System.currentTimeMillis())
} }
// TODO: remove this after we migrate to new message receiving pipeline
@JvmStatic
fun handleConfigurationMessage(context: Context, content: SignalServiceProtos.Content, senderPublicKey: String, timestamp: Long) {
synchronized(this) {
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return
if (TextSecurePreferences.getConfigurationMessageSynced(context) && !TextSecurePreferences.shouldUpdateProfile(context, timestamp)) return
if (senderPublicKey != userPublicKey) return
TextSecurePreferences.setConfigurationMessageSynced(context, true)
TextSecurePreferences.setLastProfileUpdateTime(context, timestamp)
val configurationMessage = ConfigurationMessage.fromProto(content) ?: return
val storage = MessagingConfiguration.shared.storage
val allClosedGroupPublicKeys = storage.getAllClosedGroupPublicKeys()
val threadDatabase = DatabaseFactory.getThreadDatabase(context)
val recipientDatabase = DatabaseFactory.getRecipientDatabase(context)
val ourRecipient = Recipient.from(context, Address.fromSerialized(userPublicKey),false)
for (closedGroup in configurationMessage.closedGroups) {
if (allClosedGroupPublicKeys.contains(closedGroup.publicKey)) continue
val closedGroupUpdate = DataMessage.ClosedGroupControlMessage.newBuilder()
closedGroupUpdate.type = DataMessage.ClosedGroupControlMessage.Type.NEW
closedGroupUpdate.publicKey = ByteString.copyFrom(Hex.fromStringCondensed(closedGroup.publicKey))
closedGroupUpdate.name = closedGroup.name
val encryptionKeyPair = SignalServiceProtos.KeyPair.newBuilder()
encryptionKeyPair.publicKey = ByteString.copyFrom(closedGroup.encryptionKeyPair!!.publicKey.serialize().removing05PrefixIfNeeded())
encryptionKeyPair.privateKey = ByteString.copyFrom(closedGroup.encryptionKeyPair!!.privateKey.serialize())
closedGroupUpdate.encryptionKeyPair = encryptionKeyPair.build()
closedGroupUpdate.addAllMembers(closedGroup.members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) })
closedGroupUpdate.addAllAdmins(closedGroup.admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) })
ClosedGroupsProtocolV2.handleNewClosedGroup(context, closedGroupUpdate.build(), userPublicKey, timestamp)
}
val allOpenGroups = storage.getAllOpenGroups().map { it.value.server }
for (openGroup in configurationMessage.openGroups) {
if (allOpenGroups.contains(openGroup)) continue
OpenGroupUtilities.addGroup(context, openGroup, 1)
}
if (configurationMessage.displayName.isNotEmpty()) {
TextSecurePreferences.setProfileName(context, configurationMessage.displayName)
recipientDatabase.setProfileName(ourRecipient, configurationMessage.displayName)
}
if (configurationMessage.profileKey.isNotEmpty()) {
val profileKey = Base64.encodeBytes(configurationMessage.profileKey)
ProfileKeyUtil.setEncodedProfileKey(context, profileKey)
recipientDatabase.setProfileKey(ourRecipient, configurationMessage.profileKey)
if (!configurationMessage.profilePicture.isNullOrEmpty() && TextSecurePreferences.getProfilePictureURL(context) != configurationMessage.profilePicture) {
TextSecurePreferences.setProfilePictureURL(context, configurationMessage.profilePicture)
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(ourRecipient, configurationMessage.profilePicture))
}
}
for (contact in configurationMessage.contacts) {
val address = Address.fromSerialized(contact.publicKey)
val recipient = Recipient.from(context, address, true)
if (!contact.profilePicture.isNullOrEmpty()) {
recipientDatabase.setProfileAvatar(recipient, contact.profilePicture)
}
if (contact.profileKey?.isNotEmpty() == true) {
recipientDatabase.setProfileKey(recipient, contact.profileKey)
}
if (contact.name.isNotEmpty()) {
recipientDatabase.setProfileName(recipient, contact.name)
}
recipientDatabase.setProfileSharing(recipient, true)
recipientDatabase.setRegistered(recipient, Recipient.RegisteredState.REGISTERED)
// create Thread if needed
threadDatabase.getOrCreateThreadIdFor(recipient)
}
if (configurationMessage.contacts.isNotEmpty()) {
threadDatabase.notifyUpdatedFromConfig()
}
}
}
} }

View File

@ -3,8 +3,9 @@ package org.thoughtcrime.securesms.loki.utilities
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import org.session.libsignal.service.loki.Broadcaster
class Broadcaster(private val context: Context) : org.session.libsignal.service.loki.utilities.Broadcaster { class Broadcaster(private val context: Context) : Broadcaster {
override fun broadcast(event: String) { override fun broadcast(event: String) {
val intent = Intent(event) val intent = Intent(event)

View File

@ -7,7 +7,7 @@ import android.content.IntentFilter
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
import com.opencsv.CSVReader import com.opencsv.CSVReader
import org.session.libsignal.service.loki.api.onionrequests.OnionRequestAPI import org.session.libsession.snode.OnionRequestAPI
import org.session.libsignal.utilities.ThreadUtils import org.session.libsignal.utilities.ThreadUtils
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream

View File

@ -1,10 +1,10 @@
package org.thoughtcrime.securesms.loki.utilities package org.thoughtcrime.securesms.loki.utilities
import android.content.Context import android.content.Context
import org.session.libsession.messaging.mentions.MentionsManager
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager
object MentionManagerUtilities { object MentionManagerUtilities {

View File

@ -1,10 +1,6 @@
package org.thoughtcrime.securesms.loki.utilities package org.thoughtcrime.securesms.loki.utilities
import android.content.Context import android.content.Context
import org.session.libsignal.service.loki.crypto.MnemonicCodec
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import java.io.File
import java.io.FileOutputStream
object MnemonicUtilities { object MnemonicUtilities {

View File

@ -3,12 +3,11 @@ package org.thoughtcrime.securesms.loki.utilities
import android.content.Context import android.content.Context
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.session.libsession.messaging.opengroups.OpenGroup import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.messaging.opengroups.OpenGroupAPI import org.session.libsession.messaging.open_groups.OpenGroupAPI
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.preferences.ProfileKeyUtil import org.session.libsession.utilities.preferences.ProfileKeyUtil
import org.session.libsignal.service.loki.api.opengroups.PublicChat
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.groups.GroupManager
@ -23,10 +22,10 @@ object OpenGroupUtilities {
@Throws(Exception::class) @Throws(Exception::class)
fun addGroup(context: Context, url: String, channel: Long): OpenGroup { fun addGroup(context: Context, url: String, channel: Long): OpenGroup {
// Check for an existing group. // Check for an existing group.
val groupID = PublicChat.getId(channel, url) val groupID = OpenGroup.getId(channel, url)
val threadID = GroupManager.getOpenGroupThreadID(groupID, context) val threadID = GroupManager.getOpenGroupThreadID(groupID, context)
val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID) val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
if (openGroup != null) { return OpenGroup.from(openGroup) } if (openGroup != null) { return openGroup }
// Add the new group. // Add the new group.
val application = ApplicationContext.getInstance(context) val application = ApplicationContext.getInstance(context)
@ -56,7 +55,7 @@ object OpenGroupUtilities {
@Throws(Exception::class) @Throws(Exception::class)
fun updateGroupInfo(context: Context, url: String, channel: Long) { fun updateGroupInfo(context: Context, url: String, channel: Long) {
// Check if open group has a related DB record. // Check if open group has a related DB record.
val groupId = GroupUtil.getEncodedOpenGroupID(PublicChat.getId(channel, url).toByteArray()) val groupId = GroupUtil.getEncodedOpenGroupID(OpenGroup.getId(channel, url).toByteArray())
if (!DatabaseFactory.getGroupDatabase(context).hasGroup(groupId)) { if (!DatabaseFactory.getGroupDatabase(context).hasGroup(groupId)) {
throw IllegalStateException("Attempt to update open group info for non-existent DB record: $groupId") throw IllegalStateException("Attempt to update open group info for non-existent DB record: $groupId")
} }

View File

@ -10,7 +10,7 @@ import android.widget.ListView
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.loki.utilities.toPx import org.thoughtcrime.securesms.loki.utilities.toPx
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.session.libsignal.service.loki.utilities.mentions.Mention import org.session.libsignal.service.loki.Mention
class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : ListView(context, attrs, defStyleAttr) { class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : ListView(context, attrs, defStyleAttr) {
private var mentionCandidates = listOf<Mention>() private var mentionCandidates = listOf<Mention>()

View File

@ -8,8 +8,8 @@ import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import kotlinx.android.synthetic.main.view_mention_candidate.view.* import kotlinx.android.synthetic.main.view_mention_candidate.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.messaging.opengroups.OpenGroupAPI import org.session.libsession.messaging.open_groups.OpenGroupAPI
import org.session.libsignal.service.loki.utilities.mentions.Mention import org.session.libsignal.service.loki.Mention
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) { class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) {

View File

@ -11,9 +11,9 @@ import android.util.AttributeSet
import android.view.View import android.view.View
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.snode.OnionRequestAPI
import org.thoughtcrime.securesms.loki.utilities.getColorWithID import org.thoughtcrime.securesms.loki.utilities.getColorWithID
import org.thoughtcrime.securesms.loki.utilities.toPx import org.thoughtcrime.securesms.loki.utilities.toPx
import org.session.libsignal.service.loki.api.onionrequests.OnionRequestAPI
class PathStatusView : View { class PathStatusView : View {
private val broadcastReceivers = mutableListOf<BroadcastReceiver>() private val broadcastReceivers = mutableListOf<BroadcastReceiver>()

View File

@ -11,10 +11,10 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
import kotlinx.android.synthetic.main.view_profile_picture.view.* import kotlinx.android.synthetic.main.view_profile_picture.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.messaging.avatars.ProfileContactPhoto import org.session.libsession.messaging.avatars.ProfileContactPhoto
import org.session.libsession.messaging.mentions.MentionsManager
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.messaging.threads.recipients.Recipient
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.loki.utilities.AvatarPlaceholderGenerator import org.thoughtcrime.securesms.loki.utilities.AvatarPlaceholderGenerator
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
@ -146,13 +146,13 @@ class ProfilePictureView : RelativeLayout {
private fun setProfilePictureIfNeeded(imageView: ImageView, publicKey: String, displayName: String?, @DimenRes sizeResId: Int) { private fun setProfilePictureIfNeeded(imageView: ImageView, publicKey: String, displayName: String?, @DimenRes sizeResId: Int) {
if (publicKey.isNotEmpty()) { if (publicKey.isNotEmpty()) {
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false) val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
if (imagesCached.contains(recipient.profileAvatar.orEmpty())) return if (imagesCached.contains(publicKey)) return
val signalProfilePicture = recipient.contactPhoto val signalProfilePicture = recipient.contactPhoto
if (signalProfilePicture != null && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "0" if (signalProfilePicture != null && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "0"
&& (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "") { && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "") {
glide.clear(imageView) glide.clear(imageView)
glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC).circleCrop().into(imageView) glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC).circleCrop().into(imageView)
imagesCached.add(recipient.profileAvatar.orEmpty()) imagesCached.add(publicKey)
} else { } else {
val sizeInPX = resources.getDimensionPixelSize(sizeResId) val sizeInPX = resources.getDimensionPixelSize(sizeResId)
glide.clear(imageView) glide.clear(imageView)
@ -162,7 +162,7 @@ class ProfilePictureView : RelativeLayout {
publicKey, publicKey,
displayName displayName
)).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView) )).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView)
imagesCached.add(recipient.profileAvatar.orEmpty()) imagesCached.add(publicKey)
} }
} else { } else {
imageView.setImageDrawable(null) imageView.setImageDrawable(null)

View File

@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.mms;
import android.content.Context; import android.content.Context;
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI; import org.session.libsession.messaging.file_server.FileServerAPI;
public class PushMediaConstraints extends MediaConstraints { public class PushMediaConstraints extends MediaConstraints {

View File

@ -98,13 +98,6 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@drawable/home_activity_gradient" /> android:background="@drawable/home_activity_gradient" />
<org.thoughtcrime.securesms.loki.views.NewConversationButtonSetView
android:id="@+id/newConversationButtonSet"
android:layout_width="276dp"
android:layout_height="236dp"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true" />
<LinearLayout <LinearLayout
android:id="@+id/emptyStateContainer" android:id="@+id/emptyStateContainer"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -131,6 +124,13 @@
</LinearLayout> </LinearLayout>
<org.thoughtcrime.securesms.loki.views.NewConversationButtonSetView
android:id="@+id/newConversationButtonSet"
android:layout_width="276dp"
android:layout_height="236dp"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true" />
</RelativeLayout> </RelativeLayout>
</LinearLayout> </LinearLayout>

View File

@ -1,5 +1,6 @@
package org.session.libsession.database package org.session.libsession.database
import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.messaging.sending_receiving.attachments.* import org.session.libsession.messaging.sending_receiving.attachments.*
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.utilities.DotNetAPI import org.session.libsession.messaging.utilities.DotNetAPI
@ -30,7 +31,6 @@ interface MessageDataProvider {
fun updateAttachmentAfterUploadSucceeded(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) fun updateAttachmentAfterUploadSucceeded(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult)
fun updateAttachmentAfterUploadFailed(attachmentId: Long) fun updateAttachmentAfterUploadFailed(attachmentId: Long)
// Quotes
fun getMessageForQuote(timestamp: Long, author: Address): Pair<Long, Boolean>? fun getMessageForQuote(timestamp: Long, author: Address): Pair<Long, Boolean>?
fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List<Attachment> fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List<Attachment>
fun getMessageBodyFor(timestamp: Long, author: String): String fun getMessageBodyFor(timestamp: Long, author: String): String
@ -38,4 +38,5 @@ interface MessageDataProvider {
fun getAttachmentIDsFor(messageID: Long): List<Long> fun getAttachmentIDsFor(messageID: Long): List<Long>
fun getLinkPreviewAttachmentIDFor(messageID: Long): Long? fun getLinkPreviewAttachmentIDFor(messageID: Long): Long?
fun getOpenGroup(threadID: Long): OpenGroup?
} }

View File

@ -1,25 +0,0 @@
package org.session.libsession.messaging
import android.content.Context
import org.session.libsession.database.MessageDataProvider
import org.session.libsignal.service.loki.api.crypto.SessionProtocol
class MessagingConfiguration(
val context: Context,
val storage: StorageProtocol,
val messageDataProvider: MessageDataProvider,
val sessionProtocol: SessionProtocol)
{
companion object {
lateinit var shared: MessagingConfiguration
fun configure(context: Context,
storage: StorageProtocol,
messageDataProvider: MessageDataProvider,
sessionProtocol: SessionProtocol
) {
if (Companion::shared.isInitialized) { return }
shared = MessagingConfiguration(context, storage, messageDataProvider, sessionProtocol)
}
}
}

View File

@ -0,0 +1,26 @@
package org.session.libsession.messaging
import android.content.Context
import org.session.libsession.database.MessageDataProvider
import org.session.libsignal.service.loki.api.crypto.SessionProtocol
class MessagingModuleConfiguration(
val context: Context,
val storage: StorageProtocol,
val messageDataProvider: MessageDataProvider,
val sessionProtocol: SessionProtocol)
{
companion object {
lateinit var shared: MessagingModuleConfiguration
fun configure(context: Context,
storage: StorageProtocol,
messageDataProvider: MessageDataProvider,
sessionProtocol: SessionProtocol
) {
if (Companion::shared.isInitialized) { return }
shared = MessagingModuleConfiguration(context, storage, messageDataProvider, sessionProtocol)
}
}
}

View File

@ -9,11 +9,11 @@ import org.session.libsession.messaging.jobs.MessageSendJob
import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.ConfigurationMessage
import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.Attachment
import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.messages.visible.VisibleMessage
import org.session.libsession.messaging.opengroups.OpenGroup import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.threads.GroupRecord import org.session.libsession.messaging.threads.GroupRecord
@ -21,7 +21,6 @@ import org.session.libsession.messaging.threads.recipients.Recipient.RecipientSe
import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.ecc.ECKeyPair
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.api.messages.SignalServiceGroup
import org.session.libsignal.service.internal.push.SignalServiceProtos
interface StorageProtocol { interface StorageProtocol {
@ -109,8 +108,10 @@ interface StorageProtocol {
fun createGroup(groupID: String, title: String?, members: List<Address>, avatar: SignalServiceAttachmentPointer?, relay: String?, admins: List<Address>, formationTimestamp: Long) fun createGroup(groupID: String, title: String?, members: List<Address>, avatar: SignalServiceAttachmentPointer?, relay: String?, admins: List<Address>, formationTimestamp: Long)
fun isGroupActive(groupPublicKey: String): Boolean fun isGroupActive(groupPublicKey: String): Boolean
fun setActive(groupID: String, value: Boolean) fun setActive(groupID: String, value: Boolean)
fun getZombieMember(groupID: String): Set<String>
fun removeMember(groupID: String, member: Address) fun removeMember(groupID: String, member: Address)
fun updateMembers(groupID: String, members: List<Address>) fun updateMembers(groupID: String, members: List<Address>)
fun updateZombieMembers(groupID: String, members: List<Address>)
// Closed Group // Closed Group
fun getAllClosedGroupPublicKeys(): Set<String> fun getAllClosedGroupPublicKeys(): Set<String>
fun getAllActiveClosedGroupPublicKeys(): Set<String> fun getAllActiveClosedGroupPublicKeys(): Set<String>

View File

@ -1,6 +1,5 @@
package org.session.libsession.messaging.avatars; package org.session.libsession.messaging.avatars;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;

View File

@ -1,6 +1,5 @@
package org.session.libsession.messaging.avatars; package org.session.libsession.messaging.avatars;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
@ -19,5 +18,4 @@ public interface ContactPhoto extends Key {
@Nullable Uri getUri(@NonNull Context context); @Nullable Uri getUri(@NonNull Context context);
boolean isProfilePhoto(); boolean isProfilePhoto();
} }

View File

@ -7,5 +7,4 @@ public interface FallbackContactPhoto {
public Drawable asDrawable(Context context, int color); public Drawable asDrawable(Context context, int color);
public Drawable asDrawable(Context context, int color, boolean inverted); public Drawable asDrawable(Context context, int color, boolean inverted);
} }

View File

@ -19,7 +19,6 @@ import org.session.libsession.utilities.ViewUtil;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class GeneratedContactPhoto implements FallbackContactPhoto { public class GeneratedContactPhoto implements FallbackContactPhoto {
private static final Pattern PATTERN = Pattern.compile("[^\\p{L}\\p{Nd}\\p{S}]+"); private static final Pattern PATTERN = Pattern.compile("[^\\p{L}\\p{Nd}\\p{S}]+");

View File

@ -1,13 +1,12 @@
package org.session.libsession.messaging.avatars; package org.session.libsession.messaging.avatars;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.session.libsession.messaging.MessagingConfiguration; import org.session.libsession.messaging.MessagingModuleConfiguration;
import org.session.libsession.messaging.StorageProtocol; import org.session.libsession.messaging.StorageProtocol;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.threads.GroupRecord; import org.session.libsession.messaging.threads.GroupRecord;
@ -32,7 +31,7 @@ public class GroupRecordContactPhoto implements ContactPhoto {
@Override @Override
public InputStream openInputStream(Context context) throws IOException { public InputStream openInputStream(Context context) throws IOException {
StorageProtocol groupDatabase = MessagingConfiguration.shared.getStorage(); StorageProtocol groupDatabase = MessagingModuleConfiguration.shared.getStorage();
Optional<GroupRecord> groupRecord = Optional.of(groupDatabase.getGroup(address.toGroupString())); Optional<GroupRecord> groupRecord = Optional.of(groupDatabase.getGroup(address.toGroupString()));
if (groupRecord.isPresent() && groupRecord.get().getAvatar() != null) { if (groupRecord.isPresent() && groupRecord.get().getAvatar() != null) {

View File

@ -1,6 +1,5 @@
package org.session.libsession.messaging.avatars; package org.session.libsession.messaging.avatars;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;

View File

@ -1,6 +1,5 @@
package org.session.libsession.messaging.avatars; package org.session.libsession.messaging.avatars;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;

View File

@ -1,17 +1,16 @@
package org.session.libsession.messaging.fileserver package org.session.libsession.messaging.file_server
import nl.komponents.kovenant.Promise import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.map import nl.komponents.kovenant.functional.map
import okhttp3.Request import okhttp3.Request
import org.session.libsession.messaging.utilities.DotNetAPI import org.session.libsession.messaging.utilities.DotNetAPI
import org.session.libsession.snode.OnionRequestAPI
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.service.loki.api.onionrequests.OnionRequestAPI import org.session.libsignal.service.loki.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.utilities.* import org.session.libsignal.service.loki.utilities.*
import java.net.URL import java.net.URL
import java.util.concurrent.ConcurrentHashMap
class FileServerAPI(public val server: String, userPublicKey: String, userPrivateKey: ByteArray, private val database: LokiAPIDatabaseProtocol) : DotNetAPI() { class FileServerAPI(public val server: String, userPublicKey: String, userPrivateKey: ByteArray, private val database: LokiAPIDatabaseProtocol) : DotNetAPI() {

View File

@ -1,7 +1,7 @@
package org.session.libsession.messaging.jobs package org.session.libsession.messaging.jobs
import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.fileserver.FileServerAPI import org.session.libsession.messaging.file_server.FileServerAPI
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState
import org.session.libsession.messaging.utilities.DotNetAPI import org.session.libsession.messaging.utilities.DotNetAPI
import org.session.libsignal.service.api.crypto.AttachmentCipherInputStream import org.session.libsignal.service.api.crypto.AttachmentCipherInputStream
@ -11,13 +11,10 @@ import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long): Job { class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long): Job {
override var delegate: JobDelegate? = null override var delegate: JobDelegate? = null
override var id: String? = null override var id: String? = null
override var failureCount: Int = 0 override var failureCount: Int = 0
private val MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024
// Error // Error
internal sealed class Error(val description: String) : Exception(description) { internal sealed class Error(val description: String) : Exception(description) {
object NoAttachment : Error("No such attachment.") object NoAttachment : Error("No such attachment.")
@ -28,34 +25,32 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
companion object { companion object {
val KEY: String = "AttachmentDownloadJob" val KEY: String = "AttachmentDownloadJob"
//keys used for database storage purpose // Keys used for database storage
private val KEY_ATTACHMENT_ID = "attachment_id" private val KEY_ATTACHMENT_ID = "attachment_id"
private val KEY_TS_INCOMING_MESSAGE_ID = "tsIncoming_message_id" private val KEY_TS_INCOMING_MESSAGE_ID = "tsIncoming_message_id"
} }
override fun execute() { override fun execute() {
val handleFailure: (java.lang.Exception) -> Unit = { exception -> val handleFailure: (java.lang.Exception) -> Unit = { exception ->
if(exception is Error && exception == Error.NoAttachment) { if (exception == Error.NoAttachment) {
MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) MessagingModuleConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID)
this.handlePermanentFailure(exception) this.handlePermanentFailure(exception)
} else if (exception is DotNetAPI.Error && exception == DotNetAPI.Error.ParsingFailed) { } else if (exception == DotNetAPI.Error.ParsingFailed) {
// No need to retry if the response is invalid. Most likely this means we (incorrectly) // No need to retry if the response is invalid. Most likely this means we (incorrectly)
// got a "Cannot GET ..." error from the file server. // got a "Cannot GET ..." error from the file server.
MessagingConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) MessagingModuleConfiguration.shared.messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID)
this.handlePermanentFailure(exception) this.handlePermanentFailure(exception)
} else { } else {
this.handleFailure(exception) this.handleFailure(exception)
} }
} }
try { try {
val messageDataProvider = MessagingConfiguration.shared.messageDataProvider val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
val attachment = messageDataProvider.getDatabaseAttachment(attachmentID) ?: return handleFailure(Error.NoAttachment) val attachment = messageDataProvider.getDatabaseAttachment(attachmentID) ?: return handleFailure(Error.NoAttachment)
messageDataProvider.setAttachmentState(AttachmentState.STARTED, attachmentID, this.databaseMessageID) messageDataProvider.setAttachmentState(AttachmentState.STARTED, attachmentID, this.databaseMessageID)
val tempFile = createTempFile() val tempFile = createTempFile()
FileServerAPI.shared.downloadFile(tempFile, attachment.url, MAX_ATTACHMENT_SIZE, null) FileServerAPI.shared.downloadFile(tempFile, attachment.url, null)
// DECRYPTION
// Assume we're retrieving an attachment for an open group server if the digest is not set // Assume we're retrieving an attachment for an open group server if the digest is not set
val stream = if (attachment.digest?.size ?: 0 == 0 || attachment.key.isNullOrEmpty()) FileInputStream(tempFile) val stream = if (attachment.digest?.size ?: 0 == 0 || attachment.key.isNullOrEmpty()) FileInputStream(tempFile)
@ -84,13 +79,11 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
} }
private fun createTempFile(): File { private fun createTempFile(): File {
val file = File.createTempFile("push-attachment", "tmp", MessagingConfiguration.shared.context.cacheDir) val file = File.createTempFile("push-attachment", "tmp", MessagingModuleConfiguration.shared.context.cacheDir)
file.deleteOnExit() file.deleteOnExit()
return file return file
} }
//database functions
override fun serialize(): Data { override fun serialize(): Data {
return Data.Builder().putLong(KEY_ATTACHMENT_ID, attachmentID) return Data.Builder().putLong(KEY_ATTACHMENT_ID, attachmentID)
.putLong(KEY_TS_INCOMING_MESSAGE_ID, databaseMessageID) .putLong(KEY_TS_INCOMING_MESSAGE_ID, databaseMessageID)
@ -102,6 +95,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
} }
class Factory: Job.Factory<AttachmentDownloadJob> { class Factory: Job.Factory<AttachmentDownloadJob> {
override fun create(data: Data): AttachmentDownloadJob { override fun create(data: Data): AttachmentDownloadJob {
return AttachmentDownloadJob(data.getLong(KEY_ATTACHMENT_ID), data.getLong(KEY_TS_INCOMING_MESSAGE_ID)) return AttachmentDownloadJob(data.getLong(KEY_ATTACHMENT_ID), data.getLong(KEY_TS_INCOMING_MESSAGE_ID))
} }

View File

@ -3,8 +3,8 @@ package org.session.libsession.messaging.jobs
import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output import com.esotericsoftware.kryo.io.Output
import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.fileserver.FileServerAPI import org.session.libsession.messaging.file_server.FileServerAPI
import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.DotNetAPI import org.session.libsession.messaging.utilities.DotNetAPI
@ -14,11 +14,10 @@ import org.session.libsignal.service.internal.crypto.PaddingInputStream
import org.session.libsignal.service.internal.push.PushAttachmentData import org.session.libsignal.service.internal.push.PushAttachmentData
import org.session.libsignal.service.internal.push.http.AttachmentCipherOutputStreamFactory import org.session.libsignal.service.internal.push.http.AttachmentCipherOutputStreamFactory
import org.session.libsignal.service.internal.util.Util import org.session.libsignal.service.internal.util.Util
import org.session.libsignal.service.loki.utilities.PlaintextOutputStreamFactory import org.session.libsignal.service.loki.PlaintextOutputStreamFactory
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val message: Message, val messageSendJobID: String) : Job { class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val message: Message, val messageSendJobID: String) : Job {
override var delegate: JobDelegate? = null override var delegate: JobDelegate? = null
override var id: String? = null override var id: String? = null
override var failureCount: Int = 0 override var failureCount: Int = 0
@ -34,9 +33,7 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
val TAG = AttachmentUploadJob::class.simpleName val TAG = AttachmentUploadJob::class.simpleName
val KEY: String = "AttachmentUploadJob" val KEY: String = "AttachmentUploadJob"
val maxFailureCount: Int = 20 // Keys used for database storage
//keys used for database storage purpose
private val KEY_ATTACHMENT_ID = "attachment_id" private val KEY_ATTACHMENT_ID = "attachment_id"
private val KEY_THREAD_ID = "thread_id" private val KEY_THREAD_ID = "thread_id"
private val KEY_MESSAGE = "message" private val KEY_MESSAGE = "message"
@ -45,17 +42,13 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
override fun execute() { override fun execute() {
try { try {
val attachment = MessagingConfiguration.shared.messageDataProvider.getScaledSignalAttachmentStream(attachmentID) val attachment = MessagingModuleConfiguration.shared.messageDataProvider.getScaledSignalAttachmentStream(attachmentID)
?: return handleFailure(Error.NoAttachment) ?: return handleFailure(Error.NoAttachment)
var server = FileServerAPI.shared.server
var shouldEncrypt = true
val usePadding = false val usePadding = false
val openGroup = MessagingConfiguration.shared.storage.getOpenGroup(threadID) val openGroup = MessagingModuleConfiguration.shared.storage.getOpenGroup(threadID)
openGroup?.let { val server = if (openGroup != null) openGroup.server else FileServerAPI.shared.server
server = it.server val shouldEncrypt = (openGroup == null) // Encrypt if this isn't an open group
shouldEncrypt = false
}
val attachmentKey = Util.getSecretBytes(64) val attachmentKey = Util.getSecretBytes(64)
val paddedLength = if (usePadding) PaddingInputStream.getPaddedSize(attachment.length) else attachment.length val paddedLength = if (usePadding) PaddingInputStream.getPaddedSize(attachment.length) else attachment.length
@ -67,9 +60,8 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
val uploadResult = FileServerAPI.shared.uploadAttachment(server, attachmentData) val uploadResult = FileServerAPI.shared.uploadAttachment(server, attachmentData)
handleSuccess(attachment, attachmentKey, uploadResult) handleSuccess(attachment, attachmentKey, uploadResult)
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
if (e is Error && e == Error.NoAttachment) { if (e == Error.NoAttachment) {
this.handlePermanentFailure(e) this.handlePermanentFailure(e)
} else if (e is DotNetAPI.Error && !e.isRetryable) { } else if (e is DotNetAPI.Error && !e.isRetryable) {
this.handlePermanentFailure(e) this.handlePermanentFailure(e)
@ -77,33 +69,32 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
this.handleFailure(e) this.handleFailure(e)
} }
} }
} }
private fun handleSuccess(attachment: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) { private fun handleSuccess(attachment: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) {
Log.w(TAG, "Attachment uploaded successfully.") Log.w(TAG, "Attachment uploaded successfully.")
delegate?.handleJobSucceeded(this) delegate?.handleJobSucceeded(this)
MessagingConfiguration.shared.messageDataProvider.updateAttachmentAfterUploadSucceeded(attachmentID, attachment, attachmentKey, uploadResult) MessagingModuleConfiguration.shared.messageDataProvider.updateAttachmentAfterUploadSucceeded(attachmentID, attachment, attachmentKey, uploadResult)
MessagingConfiguration.shared.storage.resumeMessageSendJobIfNeeded(messageSendJobID) MessagingModuleConfiguration.shared.storage.resumeMessageSendJobIfNeeded(messageSendJobID)
} }
private fun handlePermanentFailure(e: Exception) { private fun handlePermanentFailure(e: Exception) {
Log.w(TAG, "Attachment upload failed permanently due to error: $this.") Log.w(TAG, "Attachment upload failed permanently due to error: $this.")
delegate?.handleJobFailedPermanently(this, e) delegate?.handleJobFailedPermanently(this, e)
MessagingConfiguration.shared.messageDataProvider.updateAttachmentAfterUploadFailed(attachmentID) MessagingModuleConfiguration.shared.messageDataProvider.updateAttachmentAfterUploadFailed(attachmentID)
failAssociatedMessageSendJob(e) failAssociatedMessageSendJob(e)
} }
private fun handleFailure(e: Exception) { private fun handleFailure(e: Exception) {
Log.w(TAG, "Attachment upload failed due to error: $this.") Log.w(TAG, "Attachment upload failed due to error: $this.")
delegate?.handleJobFailed(this, e) delegate?.handleJobFailed(this, e)
if (failureCount + 1 == AttachmentUploadJob.maxFailureCount) { if (failureCount + 1 == maxFailureCount) {
failAssociatedMessageSendJob(e) failAssociatedMessageSendJob(e)
} }
} }
private fun failAssociatedMessageSendJob(e: Exception) { private fun failAssociatedMessageSendJob(e: Exception) {
val storage = MessagingConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val messageSendJob = storage.getMessageSendJob(messageSendJobID) val messageSendJob = storage.getMessageSendJob(messageSendJobID)
MessageSender.handleFailedMessageSend(this.message, e) MessageSender.handleFailedMessageSend(this.message, e)
if (messageSendJob != null) { if (messageSendJob != null) {
@ -111,10 +102,7 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
} }
} }
//database functions
override fun serialize(): Data { override fun serialize(): Data {
//serialize Message property
val kryo = Kryo() val kryo = Kryo()
kryo.isRegistrationRequired = false kryo.isRegistrationRequired = false
val serializedMessage = ByteArray(4096) val serializedMessage = ByteArray(4096)
@ -133,9 +121,9 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
} }
class Factory: Job.Factory<AttachmentUploadJob> { class Factory: Job.Factory<AttachmentUploadJob> {
override fun create(data: Data): AttachmentUploadJob { override fun create(data: Data): AttachmentUploadJob {
val serializedMessage = data.getByteArray(KEY_MESSAGE) val serializedMessage = data.getByteArray(KEY_MESSAGE)
//deserialize Message property
val kryo = Kryo() val kryo = Kryo()
val input = Input(serializedMessage) val input = Input(serializedMessage)
val message: Message = kryo.readObject(input, Message::class.java) val message: Message = kryo.readObject(input, Message::class.java)

View File

@ -12,7 +12,6 @@ import org.session.libsession.utilities.ParcelableUtil;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
// TODO AC: For now parcelable objects utilize byteArrays field to store their data into.
// Introduce a dedicated Map<String, byte[]> field specifically for parcelable needs. // Introduce a dedicated Map<String, byte[]> field specifically for parcelable needs.
public class Data { public class Data {

View File

@ -8,15 +8,13 @@ interface Job {
val maxFailureCount: Int val maxFailureCount: Int
companion object { companion object {
//keys used for database storage purpose // Keys used for database storage
private val KEY_ID = "id" private val KEY_ID = "id"
private val KEY_FAILURE_COUNT = "failure_count" private val KEY_FAILURE_COUNT = "failure_count"
} }
fun execute() fun execute()
//database functions
fun serialize(): Data fun serialize(): Data
/** /**

View File

@ -1,6 +1,7 @@
package org.session.libsession.messaging.jobs package org.session.libsession.messaging.jobs
interface JobDelegate { interface JobDelegate {
fun handleJobSucceeded(job: Job) fun handleJobSucceeded(job: Job)
fun handleJobFailed(job: Job, error: Exception) fun handleJobFailed(job: Job, error: Exception)
fun handleJobFailedPermanently(job: Job, error: Exception) fun handleJobFailedPermanently(job: Job, error: Exception)

View File

@ -3,7 +3,7 @@ package org.session.libsession.messaging.jobs
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@ -14,19 +14,17 @@ import kotlin.math.min
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.roundToLong import kotlin.math.roundToLong
class JobQueue : JobDelegate { class JobQueue : JobDelegate {
private var hasResumedPendingJobs = false // Just for debugging private var hasResumedPendingJobs = false // Just for debugging
private val jobTimestampMap = ConcurrentHashMap<Long, AtomicInteger>() private val jobTimestampMap = ConcurrentHashMap<Long, AtomicInteger>()
private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
private val scope = GlobalScope + SupervisorJob() private val scope = GlobalScope + SupervisorJob()
private val queue = Channel<Job>(UNLIMITED) private val queue = Channel<Job>(UNLIMITED)
val timer = Timer() val timer = Timer()
init { init {
// process jobs // Process jobs
scope.launch(dispatcher) { scope.launch(dispatcher) {
while (isActive) { while (isActive) {
queue.receive().let { job -> queue.receive().let { job ->
@ -49,14 +47,14 @@ class JobQueue : JobDelegate {
private fun addWithoutExecuting(job: Job) { private fun addWithoutExecuting(job: Job) {
// When adding multiple jobs in rapid succession, timestamps might not be good enough as a unique ID. To // When adding multiple jobs in rapid succession, timestamps might not be good enough as a unique ID. To
// deal with this we keep track of the number of jobs with a given timestamp and that to the end of the // deal with this we keep track of the number of jobs with a given timestamp and add that to the end of the
// timestamp to make it a unique ID. We can't use a random number because we do still want to keep track // timestamp to make it a unique ID. We can't use a random number because we do still want to keep track
// of the order in which the jobs were added. // of the order in which the jobs were added.
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()
jobTimestampMap.putIfAbsent(currentTime, AtomicInteger()) jobTimestampMap.putIfAbsent(currentTime, AtomicInteger())
job.id = currentTime.toString() + jobTimestampMap[currentTime]!!.getAndIncrement().toString() job.id = currentTime.toString() + jobTimestampMap[currentTime]!!.getAndIncrement().toString()
MessagingConfiguration.shared.storage.persistJob(job) MessagingModuleConfiguration.shared.storage.persistJob(job)
} }
fun resumePendingJobs() { fun resumePendingJobs() {
@ -67,21 +65,21 @@ class JobQueue : JobDelegate {
hasResumedPendingJobs = true hasResumedPendingJobs = true
val allJobTypes = listOf(AttachmentDownloadJob.KEY, AttachmentDownloadJob.KEY, MessageReceiveJob.KEY, MessageSendJob.KEY, NotifyPNServerJob.KEY) val allJobTypes = listOf(AttachmentDownloadJob.KEY, AttachmentDownloadJob.KEY, MessageReceiveJob.KEY, MessageSendJob.KEY, NotifyPNServerJob.KEY)
allJobTypes.forEach { type -> allJobTypes.forEach { type ->
val allPendingJobs = MessagingConfiguration.shared.storage.getAllPendingJobs(type) val allPendingJobs = MessagingModuleConfiguration.shared.storage.getAllPendingJobs(type)
allPendingJobs.sortedBy { it.id }.forEach { job -> allPendingJobs.sortedBy { it.id }.forEach { job ->
Log.i("Jobs", "Resuming pending job of type: ${job::class.simpleName}.") Log.i("Jobs", "Resuming pending job of type: ${job::class.simpleName}.")
queue.offer(job) // offer always called on unlimited capacity queue.offer(job) // Offer always called on unlimited capacity
} }
} }
} }
override fun handleJobSucceeded(job: Job) { override fun handleJobSucceeded(job: Job) {
MessagingConfiguration.shared.storage.markJobAsSucceeded(job) MessagingModuleConfiguration.shared.storage.markJobAsSucceeded(job)
} }
override fun handleJobFailed(job: Job, error: Exception) { override fun handleJobFailed(job: Job, error: Exception) {
job.failureCount += 1 job.failureCount += 1
val storage = MessagingConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
if (storage.isJobCanceled(job)) { return Log.i("Jobs", "${job::class.simpleName} canceled.")} if (storage.isJobCanceled(job)) { return Log.i("Jobs", "${job::class.simpleName} canceled.")}
storage.persistJob(job) storage.persistJob(job)
if (job.failureCount == job.maxFailureCount) { if (job.failureCount == job.maxFailureCount) {
@ -98,7 +96,7 @@ class JobQueue : JobDelegate {
override fun handleJobFailedPermanently(job: Job, error: Exception) { override fun handleJobFailedPermanently(job: Job, error: Exception) {
job.failureCount += 1 job.failureCount += 1
val storage = MessagingConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
storage.persistJob(job) storage.persistJob(job)
storage.markJobAsFailed(job) storage.markJobAsFailed(job)
} }

View File

@ -7,7 +7,6 @@ import org.session.libsession.messaging.sending_receiving.handle
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val openGroupMessageServerID: Long? = null, val openGroupID: String? = null) : Job { class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val openGroupMessageServerID: Long? = null, val openGroupID: String? = null) : Job {
override var delegate: JobDelegate? = null override var delegate: JobDelegate? = null
override var id: String? = null override var id: String? = null
override var failureCount: Int = 0 override var failureCount: Int = 0
@ -20,7 +19,7 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val
private val RECEIVE_LOCK = Object() private val RECEIVE_LOCK = Object()
//keys used for database storage purpose // Keys used for database storage
private val KEY_DATA = "data" private val KEY_DATA = "data"
private val KEY_IS_BACKGROUND_POLL = "is_background_poll" private val KEY_IS_BACKGROUND_POLL = "is_background_poll"
private val KEY_OPEN_GROUP_MESSAGE_SERVER_ID = "openGroupMessageServerID" private val KEY_OPEN_GROUP_MESSAGE_SERVER_ID = "openGroupMessageServerID"
@ -68,8 +67,6 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val
delegate?.handleJobFailed(this, e) delegate?.handleJobFailed(this, e)
} }
//database functions
override fun serialize(): Data { override fun serialize(): Data {
val builder = Data.Builder().putByteArray(KEY_DATA, data) val builder = Data.Builder().putByteArray(KEY_DATA, data)
.putBoolean(KEY_IS_BACKGROUND_POLL, isBackgroundPoll) .putBoolean(KEY_IS_BACKGROUND_POLL, isBackgroundPoll)
@ -83,6 +80,7 @@ class MessageReceiveJob(val data: ByteArray, val isBackgroundPoll: Boolean, val
} }
class Factory: Job.Factory<MessageReceiveJob> { class Factory: Job.Factory<MessageReceiveJob> {
override fun create(data: Data): MessageReceiveJob { override fun create(data: Data): MessageReceiveJob {
return MessageReceiveJob(data.getByteArray(KEY_DATA), data.getBoolean(KEY_IS_BACKGROUND_POLL), data.getLong(KEY_OPEN_GROUP_MESSAGE_SERVER_ID), data.getString(KEY_OPEN_GROUP_ID)) return MessageReceiveJob(data.getByteArray(KEY_DATA), data.getBoolean(KEY_IS_BACKGROUND_POLL), data.getLong(KEY_OPEN_GROUP_MESSAGE_SERVER_ID), data.getString(KEY_OPEN_GROUP_ID))
} }

View File

@ -3,7 +3,7 @@ package org.session.libsession.messaging.jobs
import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Input
import com.esotericsoftware.kryo.io.Output import com.esotericsoftware.kryo.io.Output
import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.messages.visible.VisibleMessage
@ -11,7 +11,6 @@ import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
class MessageSendJob(val message: Message, val destination: Destination) : Job { class MessageSendJob(val message: Message, val destination: Destination) : Job {
override var delegate: JobDelegate? = null override var delegate: JobDelegate? = null
override var id: String? = null override var id: String? = null
override var failureCount: Int = 0 override var failureCount: Int = 0
@ -22,13 +21,13 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
val TAG = MessageSendJob::class.simpleName val TAG = MessageSendJob::class.simpleName
val KEY: String = "MessageSendJob" val KEY: String = "MessageSendJob"
//keys used for database storage purpose // Keys used for database storage
private val KEY_MESSAGE = "message" private val KEY_MESSAGE = "message"
private val KEY_DESTINATION = "destination" private val KEY_DESTINATION = "destination"
} }
override fun execute() { override fun execute() {
val messageDataProvider = MessagingConfiguration.shared.messageDataProvider val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
val message = message as? VisibleMessage val message = message as? VisibleMessage
message?.let { message?.let {
if(!messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) return // The message has been deleted if(!messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) return // The message has been deleted
@ -39,7 +38,7 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
val attachments = attachmentIDs.mapNotNull { messageDataProvider.getDatabaseAttachment(it) } val attachments = attachmentIDs.mapNotNull { messageDataProvider.getDatabaseAttachment(it) }
val attachmentsToUpload = attachments.filter { it.url.isNullOrEmpty() } val attachmentsToUpload = attachments.filter { it.url.isNullOrEmpty() }
attachmentsToUpload.forEach { attachmentsToUpload.forEach {
if (MessagingConfiguration.shared.storage.getAttachmentUploadJob(it.attachmentId.rowId) != null) { if (MessagingModuleConfiguration.shared.storage.getAttachmentUploadJob(it.attachmentId.rowId) != null) {
// Wait for it to finish // Wait for it to finish
} else { } else {
val job = AttachmentUploadJob(it.attachmentId.rowId, message.threadID!!.toString(), message, id!!) val job = AttachmentUploadJob(it.attachmentId.rowId, message.threadID!!.toString(), message, id!!)
@ -72,15 +71,12 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
Log.w(TAG, "Failed to send $message::class.simpleName.") Log.w(TAG, "Failed to send $message::class.simpleName.")
val message = message as? VisibleMessage val message = message as? VisibleMessage
message?.let { message?.let {
if(!MessagingConfiguration.shared.messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) return // The message has been deleted if(!MessagingModuleConfiguration.shared.messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) return // The message has been deleted
} }
delegate?.handleJobFailed(this, error) delegate?.handleJobFailed(this, error)
} }
//database functions
override fun serialize(): Data { override fun serialize(): Data {
//serialize Message and Destination properties
val kryo = Kryo() val kryo = Kryo()
kryo.isRegistrationRequired = false kryo.isRegistrationRequired = false
val output = Output(ByteArray(4096), -1) // maxBufferSize '-1' will dynamically grow internally if we run out of room serializing the message val output = Output(ByteArray(4096), -1) // maxBufferSize '-1' will dynamically grow internally if we run out of room serializing the message
@ -101,10 +97,10 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
} }
class Factory: Job.Factory<MessageSendJob> { class Factory: Job.Factory<MessageSendJob> {
override fun create(data: Data): MessageSendJob { override fun create(data: Data): MessageSendJob {
val serializedMessage = data.getByteArray(KEY_MESSAGE) val serializedMessage = data.getByteArray(KEY_MESSAGE)
val serializedDestination = data.getByteArray(KEY_DESTINATION) val serializedDestination = data.getByteArray(KEY_DESTINATION)
//deserialize Message and Destination properties
val kryo = Kryo() val kryo = Kryo()
var input = Input(serializedMessage) var input = Input(serializedMessage)
val message = kryo.readClassAndObject(input) as Message val message = kryo.readClassAndObject(input) as Message

View File

@ -26,7 +26,7 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job {
companion object { companion object {
val KEY: String = "NotifyPNServerJob" val KEY: String = "NotifyPNServerJob"
//keys used for database storage purpose // Keys used for database storage
private val KEY_MESSAGE = "message" private val KEY_MESSAGE = "message"
} }
@ -61,18 +61,14 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job {
delegate?.handleJobFailed(this, error) delegate?.handleJobFailed(this, error)
} }
//database functions
override fun serialize(): Data { override fun serialize(): Data {
//serialize SnodeMessage property
val kryo = Kryo() val kryo = Kryo()
kryo.isRegistrationRequired = false kryo.isRegistrationRequired = false
val serializedMessage = ByteArray(4096) val serializedMessage = ByteArray(4096)
val output = Output(serializedMessage) val output = Output(serializedMessage)
kryo.writeObject(output, message) kryo.writeObject(output, message)
output.close() output.close()
return Data.Builder().putByteArray(KEY_MESSAGE, serializedMessage) return Data.Builder().putByteArray(KEY_MESSAGE, serializedMessage).build();
.build();
} }
override fun getFactoryKey(): String { override fun getFactoryKey(): String {
@ -80,9 +76,9 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job {
} }
class Factory: Job.Factory<NotifyPNServerJob> { class Factory: Job.Factory<NotifyPNServerJob> {
override fun create(data: Data): NotifyPNServerJob { override fun create(data: Data): NotifyPNServerJob {
val serializedMessage = data.getByteArray(KEY_MESSAGE) val serializedMessage = data.getByteArray(KEY_MESSAGE)
//deserialize SnodeMessage property
val kryo = Kryo() val kryo = Kryo()
val input = Input(serializedMessage) val input = Input(serializedMessage)
val message: SnodeMessage = kryo.readObject(input, SnodeMessage::class.java) val message: SnodeMessage = kryo.readObject(input, SnodeMessage::class.java)

View File

@ -1,7 +1,5 @@
package org.session.libsession.messaging.jobs package org.session.libsession.messaging.jobs
import java.util.*
class SessionJobInstantiator(private val jobFactories: Map<String, Job.Factory<out Job>>) { class SessionJobInstantiator(private val jobFactories: Map<String, Job.Factory<out Job>>) {
fun instantiate(jobFactoryKey: String, data: Data): Job { fun instantiate(jobFactoryKey: String, data: Data): Job {

View File

@ -1,7 +1,5 @@
package org.session.libsession.messaging.jobs package org.session.libsession.messaging.jobs
import java.util.*
class SessionJobManagerFactories { class SessionJobManagerFactories {
companion object { companion object {

View File

@ -1,19 +1,20 @@
package org.session.libsignal.service.loki.utilities.mentions package org.session.libsession.messaging.mentions
import org.session.libsignal.service.loki.database.LokiThreadDatabaseProtocol import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol import org.session.libsignal.service.loki.Mention
class MentionsManager(private val userPublicKey: String, private val threadDatabase: LokiThreadDatabaseProtocol, import org.session.libsignal.service.loki.LokiUserDatabaseProtocol
private val userDatabase: LokiUserDatabaseProtocol) {
class MentionsManager(private val userPublicKey: String, private val userDatabase: LokiUserDatabaseProtocol) {
var userPublicKeyCache = mutableMapOf<Long, Set<String>>() // Thread ID to set of user hex encoded public keys var userPublicKeyCache = mutableMapOf<Long, Set<String>>() // Thread ID to set of user hex encoded public keys
companion object { companion object {
public lateinit var shared: MentionsManager public lateinit var shared: MentionsManager
public fun configureIfNeeded(userPublicKey: String, threadDatabase: LokiThreadDatabaseProtocol, userDatabase: LokiUserDatabaseProtocol) { public fun configureIfNeeded(userPublicKey: String, userDatabase: LokiUserDatabaseProtocol) {
if (::shared.isInitialized) { return; } if (::shared.isInitialized) { return; }
shared = MentionsManager(userPublicKey, threadDatabase, userDatabase) shared = MentionsManager(userPublicKey, userDatabase)
} }
} }
@ -30,7 +31,7 @@ class MentionsManager(private val userPublicKey: String, private val threadDatab
// Prepare // Prepare
val cache = userPublicKeyCache[threadID] ?: return listOf() val cache = userPublicKeyCache[threadID] ?: return listOf()
// Gather candidates // Gather candidates
val publicChat = threadDatabase.getPublicChat(threadID) val publicChat = MessagingModuleConfiguration.shared.messageDataProvider.getOpenGroup(threadID)
var candidates: List<Mention> = cache.mapNotNull { publicKey -> var candidates: List<Mention> = cache.mapNotNull { publicKey ->
val displayName: String? val displayName: String?
if (publicChat != null) { if (publicChat != null) {

View File

@ -1,6 +1,6 @@
package org.session.libsession.messaging.messages package org.session.libsession.messaging.messages
import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.service.loki.utilities.toHexString
@ -29,8 +29,8 @@ sealed class Destination {
ClosedGroup(groupPublicKey) ClosedGroup(groupPublicKey)
} }
address.isOpenGroup -> { address.isOpenGroup -> {
val threadID = MessagingConfiguration.shared.storage.getThreadID(address.contactIdentifier())!! val threadID = MessagingModuleConfiguration.shared.storage.getThreadID(address.contactIdentifier())!!
val openGroup = MessagingConfiguration.shared.storage.getOpenGroup(threadID)!! val openGroup = MessagingModuleConfiguration.shared.storage.getOpenGroup(threadID)!!
OpenGroup(openGroup.channel, openGroup.server) OpenGroup(openGroup.channel, openGroup.server)
} }
else -> { else -> {

View File

@ -5,7 +5,6 @@ import org.session.libsession.utilities.GroupUtil
import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.push.SignalServiceProtos
abstract class Message { abstract class Message {
var id: Long? = null var id: Long? = null
var threadID: Long? = null var threadID: Long? = null
var sentTimestamp: Long? = null var sentTimestamp: Long? = null
@ -15,10 +14,9 @@ abstract class Message {
var groupPublicKey: String? = null var groupPublicKey: String? = null
var openGroupServerMessageID: Long? = null var openGroupServerMessageID: Long? = null
open val ttl: Long = 2 * 24 * 60 * 60 * 1000 open val ttl: Long = 14 * 24 * 60 * 60 * 1000
open val isSelfSendValid: Boolean = false open val isSelfSendValid: Boolean = false
// validation
open fun isValid(): Boolean { open fun isValid(): Boolean {
sentTimestamp?.let { sentTimestamp?.let {
if (it <= 0) return false if (it <= 0) return false

View File

@ -1,7 +1,7 @@
package org.session.libsession.messaging.messages.control package org.session.libsession.messaging.messages.control
import com.google.protobuf.ByteString import com.google.protobuf.ByteString
import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.messaging.threads.recipients.Recipient
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
@ -19,8 +19,8 @@ class ClosedGroupControlMessage() : ControlMessage() {
override val ttl: Long = run { override val ttl: Long = run {
when (kind) { when (kind) {
is Kind.EncryptionKeyPair -> return@run 4 * 24 * 60 * 60 * 1000 is Kind.EncryptionKeyPair -> 14 * 24 * 60 * 60 * 1000
else -> return@run 2 * 24 * 60 * 60 * 1000 else -> 14 * 24 * 60 * 60 * 1000
} }
} }
@ -28,15 +28,10 @@ class ClosedGroupControlMessage() : ControlMessage() {
var kind: Kind? = null var kind: Kind? = null
// Kind enum
sealed class Kind { sealed class Kind {
class New(var publicKey: ByteString, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List<ByteString>, var admins: List<ByteString>) : Kind() { class New(var publicKey: ByteString, var name: String, var encryptionKeyPair: ECKeyPair?, var members: List<ByteString>, var admins: List<ByteString>) : Kind() {
internal constructor(): this(ByteString.EMPTY, "", null, listOf(), listOf()) internal constructor(): this(ByteString.EMPTY, "", null, listOf(), listOf())
} }
/// - Note: Deprecated in favor of more explicit group updates.
class Update(var name: String, var members: List<ByteString>) : Kind() {
internal constructor(): this("", listOf())
}
/// An encryption key pair encrypted for each member individually. /// An encryption key pair encrypted for each member individually.
/// ///
/// - Note: `publicKey` is only set when an encryption key pair is sent in a one-to-one context (i.e. not in a group). /// - Note: `publicKey` is only set when an encryption key pair is sent in a one-to-one context (i.e. not in a group).
@ -53,18 +48,15 @@ class ClosedGroupControlMessage() : ControlMessage() {
internal constructor(): this(listOf()) internal constructor(): this(listOf())
} }
class MemberLeft() : Kind() class MemberLeft() : Kind()
class EncryptionKeyPairRequest(): Kind()
val description: String = val description: String =
when(this) { when(this) {
is New -> "new" is New -> "new"
is Update -> "update"
is EncryptionKeyPair -> "encryptionKeyPair" is EncryptionKeyPair -> "encryptionKeyPair"
is NameChange -> "nameChange" is NameChange -> "nameChange"
is MembersAdded -> "membersAdded" is MembersAdded -> "membersAdded"
is MembersRemoved -> "membersRemoved" is MembersRemoved -> "membersRemoved"
is MemberLeft -> "memberLeft" is MemberLeft -> "memberLeft"
is EncryptionKeyPairRequest -> "encryptionKeyPairRequest"
} }
} }
@ -80,7 +72,6 @@ class ClosedGroupControlMessage() : ControlMessage() {
val publicKey = closedGroupControlMessageProto.publicKey ?: return null val publicKey = closedGroupControlMessageProto.publicKey ?: return null
val name = closedGroupControlMessageProto.name ?: return null val name = closedGroupControlMessageProto.name ?: return null
val encryptionKeyPairAsProto = closedGroupControlMessageProto.encryptionKeyPair ?: return null val encryptionKeyPairAsProto = closedGroupControlMessageProto.encryptionKeyPair ?: return null
try { try {
val encryptionKeyPair = ECKeyPair(DjbECPublicKey(encryptionKeyPairAsProto.publicKey.toByteArray()), DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray())) val encryptionKeyPair = ECKeyPair(DjbECPublicKey(encryptionKeyPairAsProto.publicKey.toByteArray()), DjbECPrivateKey(encryptionKeyPairAsProto.privateKey.toByteArray()))
kind = Kind.New(publicKey, name, encryptionKeyPair, closedGroupControlMessageProto.membersList, closedGroupControlMessageProto.adminsList) kind = Kind.New(publicKey, name, encryptionKeyPair, closedGroupControlMessageProto.membersList, closedGroupControlMessageProto.adminsList)
@ -89,10 +80,6 @@ class ClosedGroupControlMessage() : ControlMessage() {
return null return null
} }
} }
DataMessage.ClosedGroupControlMessage.Type.UPDATE -> {
val name = closedGroupControlMessageProto.name ?: return null
kind = Kind.Update(name, closedGroupControlMessageProto.membersList)
}
DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR -> { DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR -> {
val publicKey = closedGroupControlMessageProto.publicKey val publicKey = closedGroupControlMessageProto.publicKey
val wrappers = closedGroupControlMessageProto.wrappersList.mapNotNull { KeyPairWrapper.fromProto(it) } val wrappers = closedGroupControlMessageProto.wrappersList.mapNotNull { KeyPairWrapper.fromProto(it) }
@ -111,20 +98,15 @@ class ClosedGroupControlMessage() : ControlMessage() {
DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> { DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> {
kind = Kind.MemberLeft() kind = Kind.MemberLeft()
} }
DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR_REQUEST -> {
kind = Kind.EncryptionKeyPairRequest()
}
} }
return ClosedGroupControlMessage(kind) return ClosedGroupControlMessage(kind)
} }
} }
// constructor
internal constructor(kind: Kind?) : this() { internal constructor(kind: Kind?) : this() {
this.kind = kind this.kind = kind
} }
// validation
override fun isValid(): Boolean { override fun isValid(): Boolean {
if (!super.isValid()) return false if (!super.isValid()) return false
val kind = kind ?: return false val kind = kind ?: return false
@ -133,13 +115,11 @@ class ClosedGroupControlMessage() : ControlMessage() {
!kind.publicKey.isEmpty && kind.name.isNotEmpty() && kind.encryptionKeyPair!!.publicKey != null !kind.publicKey.isEmpty && kind.name.isNotEmpty() && kind.encryptionKeyPair!!.publicKey != null
&& kind.encryptionKeyPair!!.privateKey != null && kind.members.isNotEmpty() && kind.admins.isNotEmpty() && kind.encryptionKeyPair!!.privateKey != null && kind.members.isNotEmpty() && kind.admins.isNotEmpty()
} }
is Kind.Update -> kind.name.isNotEmpty()
is Kind.EncryptionKeyPair -> true is Kind.EncryptionKeyPair -> true
is Kind.NameChange -> kind.name.isNotEmpty() is Kind.NameChange -> kind.name.isNotEmpty()
is Kind.MembersAdded -> kind.members.isNotEmpty() is Kind.MembersAdded -> kind.members.isNotEmpty()
is Kind.MembersRemoved -> kind.members.isNotEmpty() is Kind.MembersRemoved -> kind.members.isNotEmpty()
is Kind.MemberLeft -> true is Kind.MemberLeft -> true
is Kind.EncryptionKeyPairRequest -> true
} }
} }
@ -163,14 +143,9 @@ class ClosedGroupControlMessage() : ControlMessage() {
closedGroupControlMessage.addAllMembers(kind.members) closedGroupControlMessage.addAllMembers(kind.members)
closedGroupControlMessage.addAllAdmins(kind.admins) closedGroupControlMessage.addAllAdmins(kind.admins)
} }
is Kind.Update -> {
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.UPDATE
closedGroupControlMessage.name = kind.name
closedGroupControlMessage.addAllMembers(kind.members)
}
is Kind.EncryptionKeyPair -> { is Kind.EncryptionKeyPair -> {
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR
closedGroupControlMessage.publicKey = kind.publicKey closedGroupControlMessage.publicKey = kind.publicKey ?: ByteString.EMPTY
closedGroupControlMessage.addAllWrappers(kind.wrappers.map { it.toProto() }) closedGroupControlMessage.addAllWrappers(kind.wrappers.map { it.toProto() })
} }
is Kind.NameChange -> { is Kind.NameChange -> {
@ -188,9 +163,6 @@ class ClosedGroupControlMessage() : ControlMessage() {
is Kind.MemberLeft -> { is Kind.MemberLeft -> {
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT
} }
is Kind.EncryptionKeyPairRequest -> {
// TODO: closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR_REQUEST
}
} }
val contentProto = SignalServiceProtos.Content.newBuilder() val contentProto = SignalServiceProtos.Content.newBuilder()
val dataMessageProto = DataMessage.newBuilder() val dataMessageProto = DataMessage.newBuilder()
@ -200,7 +172,7 @@ class ClosedGroupControlMessage() : ControlMessage() {
// Expiration timer // Expiration timer
// TODO: We * want * expiration timer updates to be explicit. But currently Android will disable the expiration timer for a conversation // TODO: We * want * expiration timer updates to be explicit. But currently Android will disable the expiration timer for a conversation
// if it receives a message without the current expiration timer value attached to it... // if it receives a message without the current expiration timer value attached to it...
dataMessageProto.expireTimer = Recipient.from(MessagingConfiguration.shared.context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(recipient!!)), false).expireMessages dataMessageProto.expireTimer = Recipient.from(MessagingModuleConfiguration.shared.context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(recipient!!)), false).expireMessages
contentProto.dataMessage = dataMessageProto.build() contentProto.dataMessage = dataMessageProto.build()
return contentProto.build() return contentProto.build()
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -1,7 +1,7 @@
package org.session.libsession.messaging.messages.control package org.session.libsession.messaging.messages.control
import com.google.protobuf.ByteString import com.google.protobuf.ByteString
import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
@ -26,6 +26,7 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
} }
companion object { companion object {
fun fromProto(proto: SignalServiceProtos.ConfigurationMessage.ClosedGroup): ClosedGroup? { fun fromProto(proto: SignalServiceProtos.ConfigurationMessage.ClosedGroup): ClosedGroup? {
if (!proto.hasPublicKey() || !proto.hasName() || !proto.hasEncryptionKeyPair()) return null if (!proto.hasPublicKey() || !proto.hasName() || !proto.hasEncryptionKeyPair()) return null
val publicKey = proto.publicKey.toByteArray().toHexString() val publicKey = proto.publicKey.toByteArray().toHexString()
@ -58,6 +59,7 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
internal constructor(): this("", "", null, null) internal constructor(): this("", "", null, null)
companion object { companion object {
fun fromProto(proto: SignalServiceProtos.ConfigurationMessage.Contact): Contact? { fun fromProto(proto: SignalServiceProtos.ConfigurationMessage.Contact): Contact? {
if (!proto.hasName() || !proto.hasProfileKey()) return null if (!proto.hasName() || !proto.hasProfileKey()) return null
val publicKey = proto.publicKey.toByteArray().toHexString() val publicKey = proto.publicKey.toByteArray().toHexString()
@ -87,7 +89,6 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
} }
} }
override val ttl: Long = 4 * 24 * 60 * 60 * 1000
override val isSelfSendValid: Boolean = true override val isSelfSendValid: Boolean = true
companion object { companion object {
@ -95,7 +96,7 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
fun getCurrent(contacts: List<Contact>): ConfigurationMessage? { fun getCurrent(contacts: List<Contact>): ConfigurationMessage? {
val closedGroups = mutableListOf<ClosedGroup>() val closedGroups = mutableListOf<ClosedGroup>()
val openGroups = mutableListOf<String>() val openGroups = mutableListOf<String>()
val sharedConfig = MessagingConfiguration.shared val sharedConfig = MessagingModuleConfiguration.shared
val storage = sharedConfig.storage val storage = sharedConfig.storage
val context = sharedConfig.context val context = sharedConfig.context
val displayName = TextSecurePreferences.getProfileName(context) ?: return null val displayName = TextSecurePreferences.getProfileName(context) ?: return null

View File

@ -6,7 +6,6 @@ import org.session.libsignal.utilities.logging.Log
class DataExtractionNotification(): ControlMessage() { class DataExtractionNotification(): ControlMessage() {
var kind: Kind? = null var kind: Kind? = null
// Kind enum
sealed class Kind { sealed class Kind {
class Screenshot() : Kind() class Screenshot() : Kind()
class MediaSaved(val timestamp: Long) : Kind() class MediaSaved(val timestamp: Long) : Kind()
@ -35,12 +34,10 @@ class DataExtractionNotification(): ControlMessage() {
} }
} }
//constructor
internal constructor(kind: Kind) : this() { internal constructor(kind: Kind) : this() {
this.kind = kind this.kind = kind
} }
// MARK: Validation
override fun isValid(): Boolean { override fun isValid(): Boolean {
if (!super.isValid()) return false if (!super.isValid()) return false
val kind = kind ?: return false val kind = kind ?: return false

View File

@ -1,12 +1,11 @@
package org.session.libsession.messaging.messages.control package org.session.libsession.messaging.messages.control
import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.messages.visible.VisibleMessage
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.push.SignalServiceProtos
class ExpirationTimerUpdate() : ControlMessage() { class ExpirationTimerUpdate() : ControlMessage() {
/// In the case of a sync message, the public key of the person the message was targeted at. /// In the case of a sync message, the public key of the person the message was targeted at.
/// - Note: `nil` if this isn't a sync message. /// - Note: `nil` if this isn't a sync message.
var syncTarget: String? = null var syncTarget: String? = null
@ -27,7 +26,6 @@ class ExpirationTimerUpdate() : ControlMessage() {
} }
} }
//constructor
internal constructor(syncTarget: String?, duration: Int) : this() { internal constructor(syncTarget: String?, duration: Int) : this() {
this.syncTarget = syncTarget this.syncTarget = syncTarget
this.duration = duration this.duration = duration
@ -38,7 +36,6 @@ class ExpirationTimerUpdate() : ControlMessage() {
this.duration = duration this.duration = duration
} }
// validation
override fun isValid(): Boolean { override fun isValid(): Boolean {
if (!super.isValid()) return false if (!super.isValid()) return false
return duration != null return duration != null
@ -58,7 +55,7 @@ class ExpirationTimerUpdate() : ControlMessage() {
dataMessageProto.syncTarget = syncTarget dataMessageProto.syncTarget = syncTarget
} }
// Group context // Group context
if (MessagingConfiguration.shared.storage.isClosedGroup(recipient!!)) { if (MessagingModuleConfiguration.shared.storage.isClosedGroup(recipient!!)) {
try { try {
setGroupContext(dataMessageProto) setGroupContext(dataMessageProto)
} catch(e: Exception) { } catch(e: Exception) {

View File

@ -4,7 +4,6 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
class ReadReceipt() : ControlMessage() { class ReadReceipt() : ControlMessage() {
var timestamps: List<Long>? = null var timestamps: List<Long>? = null
companion object { companion object {
@ -19,12 +18,10 @@ class ReadReceipt() : ControlMessage() {
} }
} }
//constructor
internal constructor(timestamps: List<Long>?) : this() { internal constructor(timestamps: List<Long>?) : this() {
this.timestamps = timestamps this.timestamps = timestamps
} }
// validation
override fun isValid(): Boolean { override fun isValid(): Boolean {
if (!super.isValid()) return false if (!super.isValid()) return false
val timestamps = timestamps ?: return false val timestamps = timestamps ?: return false

View File

@ -4,8 +4,8 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
class TypingIndicator() : ControlMessage() { class TypingIndicator() : ControlMessage() {
override val ttl: Long = 30 * 1000 override val ttl: Long = 30 * 1000
var kind: Kind? = null
companion object { companion object {
const val TAG = "TypingIndicator" const val TAG = "TypingIndicator"
@ -17,11 +17,8 @@ class TypingIndicator() : ControlMessage() {
} }
} }
// Kind enum
enum class Kind { enum class Kind {
STARTED, STARTED, STOPPED;
STOPPED,
;
companion object { companion object {
@JvmStatic @JvmStatic
@ -40,14 +37,10 @@ class TypingIndicator() : ControlMessage() {
} }
} }
var kind: Kind? = null
//constructor
internal constructor(kind: Kind) : this() { internal constructor(kind: Kind) : this() {
this.kind = kind this.kind = kind
} }
// validation
override fun isValid(): Boolean { override fun isValid(): Boolean {
if (!super.isValid()) return false if (!super.isValid()) return false
return kind != null return kind != null

View File

@ -1,7 +1,5 @@
package org.session.libsession.messaging.messages.signal; package org.session.libsession.messaging.messages.signal;
import static org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext;
public class IncomingGroupMessage extends IncomingTextMessage { public class IncomingGroupMessage extends IncomingTextMessage {
private final String groupID; private final String groupID;

View File

@ -3,10 +3,10 @@ package org.session.libsession.messaging.messages.signal;
import org.session.libsession.messaging.messages.visible.VisibleMessage; import org.session.libsession.messaging.messages.visible.VisibleMessage;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment; import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment;
import org.session.libsession.messaging.sending_receiving.dataextraction.DataExtractionNotificationInfoMessage; import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage;
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact; import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
import org.session.libsession.messaging.threads.Address; import org.session.libsession.messaging.threads.Address;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
import org.session.libsession.utilities.GroupUtil; import org.session.libsession.utilities.GroupUtil;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;

View File

@ -6,14 +6,10 @@ import androidx.annotation.Nullable;
import org.session.libsession.messaging.threads.DistributionTypes; import org.session.libsession.messaging.threads.DistributionTypes;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact; import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsignal.utilities.Base64;
import org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext;
import java.io.IOException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;

View File

@ -9,7 +9,7 @@ import org.session.libsession.database.documents.IdentityKeyMismatch;
import org.session.libsession.database.documents.NetworkFailure; import org.session.libsession.database.documents.NetworkFailure;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact; import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;

View File

@ -5,7 +5,7 @@ import androidx.annotation.Nullable;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact; import org.session.libsession.messaging.sending_receiving.sharecontacts.Contact;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.threads.recipients.Recipient;

View File

@ -11,7 +11,6 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos
import java.io.File import java.io.File
class Attachment { class Attachment {
var fileName: String? = null var fileName: String? = null
var contentType: String? = null var contentType: String? = null
var key: ByteArray? = null var key: ByteArray? = null
@ -23,6 +22,7 @@ class Attachment {
var url: String? = null var url: String? = null
companion object { companion object {
fun fromProto(proto: SignalServiceProtos.AttachmentPointer): Attachment { fun fromProto(proto: SignalServiceProtos.AttachmentPointer): Attachment {
val result = Attachment() val result = Attachment()
result.fileName = proto.fileName result.fileName = proto.fileName
@ -88,7 +88,6 @@ class Attachment {
GENERIC GENERIC
} }
// validation
fun isValid(): Boolean { fun isValid(): Boolean {
// key and digest can be nil for open group attachments // key and digest can be nil for open group attachments
return (contentType != null && kind != null && size != null && sizeInBytes != null && url != null) return (contentType != null && kind != null && size != null && sizeInBytes != null && url != null)

View File

@ -1,17 +0,0 @@
package org.session.libsession.messaging.messages.visible
import org.session.libsession.database.MessageDataProvider
import org.session.libsignal.service.internal.push.SignalServiceProtos
class Contact() {
companion object {
fun fromProto(proto: SignalServiceProtos.Content): Contact? {
TODO("Not yet implemented")
}
}
fun toProto(): SignalServiceProtos.DataMessage.Contact? {
TODO("Not yet implemented")
}
}

View File

@ -1,12 +1,11 @@
package org.session.libsession.messaging.messages.visible package org.session.libsession.messaging.messages.visible
import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview as SignalLinkPreiview import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview as SignalLinkPreiview
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.push.SignalServiceProtos
class LinkPreview() { class LinkPreview() {
var title: String? = null var title: String? = null
var url: String? = null var url: String? = null
var attachmentID: Long? = 0 var attachmentID: Long? = 0
@ -29,15 +28,12 @@ class LinkPreview() {
} }
} }
//constructor
internal constructor(title: String?, url: String, attachmentID: Long?) : this() { internal constructor(title: String?, url: String, attachmentID: Long?) : this() {
this.title = title this.title = title
this.url = url this.url = url
this.attachmentID = attachmentID this.attachmentID = attachmentID
} }
// validation
fun isValid(): Boolean { fun isValid(): Boolean {
return (title != null && url != null && attachmentID != null) return (title != null && url != null && attachmentID != null)
} }
@ -53,7 +49,7 @@ class LinkPreview() {
title?.let { linkPreviewProto.title = title } title?.let { linkPreviewProto.title = title }
val attachmentID = attachmentID val attachmentID = attachmentID
attachmentID?.let { attachmentID?.let {
MessagingConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(attachmentID)?.let { MessagingModuleConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(attachmentID)?.let {
val attachmentProto = Attachment.createAttachmentPointer(it) val attachmentProto = Attachment.createAttachmentPointer(it)
linkPreviewProto.image = attachmentProto linkPreviewProto.image = attachmentProto
} }

View File

@ -5,7 +5,6 @@ import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.push.SignalServiceProtos
class Profile() { class Profile() {
var displayName: String? = null var displayName: String? = null
var profileKey: ByteArray? = null var profileKey: ByteArray? = null
var profilePictureURL: String? = null var profilePictureURL: String? = null
@ -27,7 +26,6 @@ class Profile() {
} }
} }
//constructor
internal constructor(displayName: String, profileKey: ByteArray? = null, profilePictureURL: String? = null) : this() { internal constructor(displayName: String, profileKey: ByteArray? = null, profilePictureURL: String? = null) : this() {
this.displayName = displayName this.displayName = displayName
this.profileKey = profileKey this.profileKey = profileKey

View File

@ -1,14 +1,13 @@
package org.session.libsession.messaging.messages.visible package org.session.libsession.messaging.messages.visible
import com.goterl.lazycode.lazysodium.BuildConfig import com.goterl.lazycode.lazysodium.BuildConfig
import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel as SignalQuote import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel as SignalQuote
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.push.SignalServiceProtos
class Quote() { class Quote() {
var timestamp: Long? = 0 var timestamp: Long? = 0
var publicKey: String? = null var publicKey: String? = null
var text: String? = null var text: String? = null
@ -34,7 +33,6 @@ class Quote() {
} }
} }
//constructor
internal constructor(timestamp: Long, publicKey: String, text: String?, attachmentID: Long?) : this() { internal constructor(timestamp: Long, publicKey: String, text: String?, attachmentID: Long?) : this() {
this.timestamp = timestamp this.timestamp = timestamp
this.publicKey = publicKey this.publicKey = publicKey
@ -42,7 +40,6 @@ class Quote() {
this.attachmentID = attachmentID this.attachmentID = attachmentID
} }
// validation
fun isValid(): Boolean { fun isValid(): Boolean {
return (timestamp != null && publicKey != null) return (timestamp != null && publicKey != null)
} }
@ -70,7 +67,7 @@ class Quote() {
private fun addAttachmentsIfNeeded(quoteProto: SignalServiceProtos.DataMessage.Quote.Builder) { private fun addAttachmentsIfNeeded(quoteProto: SignalServiceProtos.DataMessage.Quote.Builder) {
if (attachmentID == null) return if (attachmentID == null) return
val attachment = MessagingConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(attachmentID!!) val attachment = MessagingModuleConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(attachmentID!!)
if (attachment == null) { if (attachment == null) {
Log.w(TAG, "Ignoring invalid attachment for quoted message.") Log.w(TAG, "Ignoring invalid attachment for quoted message.")
return return

View File

@ -1,7 +1,7 @@
package org.session.libsession.messaging.messages.visible package org.session.libsession.messaging.messages.visible
import com.goterl.lazycode.lazysodium.BuildConfig import com.goterl.lazycode.lazysodium.BuildConfig
import org.session.libsession.messaging.MessagingConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
@ -12,13 +12,11 @@ import org.session.libsignal.utilities.logging.Log
import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment
class VisibleMessage : Message() { class VisibleMessage : Message() {
var syncTarget: String? = null var syncTarget: String? = null
var text: String? = null var text: String? = null
val attachmentIDs: MutableList<Long> = mutableListOf() val attachmentIDs: MutableList<Long> = mutableListOf()
var quote: Quote? = null var quote: Quote? = null
var linkPreview: LinkPreview? = null var linkPreview: LinkPreview? = null
var contact: Contact? = null
var profile: Profile? = null var profile: Profile? = null
override val isSelfSendValid: Boolean = true override val isSelfSendValid: Boolean = true
@ -60,10 +58,9 @@ class VisibleMessage : Message() {
} }
fun isMediaMessage(): Boolean { fun isMediaMessage(): Boolean {
return attachmentIDs.isNotEmpty() || quote != null || linkPreview != null || contact != null return attachmentIDs.isNotEmpty() || quote != null || linkPreview != null
} }
// validation
override fun isValid(): Boolean { override fun isValid(): Boolean {
if (!super.isValid()) return false if (!super.isValid()) return false
if (attachmentIDs.isNotEmpty()) return true if (attachmentIDs.isNotEmpty()) return true
@ -98,7 +95,7 @@ class VisibleMessage : Message() {
} }
} }
//Attachments //Attachments
val attachments = attachmentIDs.mapNotNull { MessagingConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(it) } val attachments = attachmentIDs.mapNotNull { MessagingModuleConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(it) }
if (!attachments.all { !it.url.isNullOrEmpty() }) { if (!attachments.all { !it.url.isNullOrEmpty() }) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
//TODO equivalent to iOS's preconditionFailure //TODO equivalent to iOS's preconditionFailure
@ -111,8 +108,8 @@ class VisibleMessage : Message() {
// Expiration timer // Expiration timer
// TODO: We * want * expiration timer updates to be explicit. But currently Android will disable the expiration timer for a conversation // TODO: We * want * expiration timer updates to be explicit. But currently Android will disable the expiration timer for a conversation
// if it receives a message without the current expiration timer value attached to it... // if it receives a message without the current expiration timer value attached to it...
val storage = MessagingConfiguration.shared.storage val storage = MessagingModuleConfiguration.shared.storage
val context = MessagingConfiguration.shared.context val context = MessagingModuleConfiguration.shared.context
val expiration = if (storage.isClosedGroup(recipient!!)) Recipient.from(context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(recipient!!)), false).expireMessages val expiration = if (storage.isClosedGroup(recipient!!)) Recipient.from(context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(recipient!!)), false).expireMessages
else Recipient.from(context, Address.fromSerialized(recipient!!), false).expireMessages else Recipient.from(context, Address.fromSerialized(recipient!!), false).expireMessages
dataMessage.expireTimer = expiration dataMessage.expireTimer = expiration

Some files were not shown because too many files have changed in this diff Show More