From 49c3ffd9cae347b639d33ab7b68e82d0f3076678 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 21 May 2021 15:02:34 +1000 Subject: [PATCH] Remove redundant code --- .../securesms/ApplicationContext.java | 34 +- .../securesms/MessageDetailsActivity.java | 7 +- .../attachments/DatabaseAttachmentProvider.kt | 9 +- .../securesms/components/QuoteView.java | 12 +- .../conversation/ConversationActivity.java | 24 +- .../conversation/ConversationFragment.java | 52 +-- .../conversation/ConversationItem.java | 28 +- .../securesms/database/Storage.kt | 201 ++------- .../securesms/jobs/AvatarDownloadJob.java | 2 +- .../jobs/RetrieveProfileAvatarJob.java | 2 +- .../loki/activities/BackupRestoreActivity.kt | 46 +- .../securesms/loki/activities/HomeActivity.kt | 11 +- .../loki/activities/LinkDeviceActivity.kt | 1 - .../loki/activities/PNModeActivity.kt | 1 - .../loki/activities/SettingsActivity.kt | 4 - .../securesms/loki/api/OpenGroupManager.kt | 22 +- .../loki/api/PublicChatInfoUpdateWorker.kt | 39 +- .../loki/database/LokiThreadDatabase.kt | 56 --- .../loki/database/LokiUserDatabase.kt | 21 - .../loki/protocol/SessionMetaProtocol.kt | 24 -- .../loki/utilities/MentionUtilities.kt | 3 - .../loki/utilities/NotificationUtilities.kt | 9 +- .../loki/utilities/OpenGroupUtilities.kt | 25 -- .../views/MentionCandidateSelectionView.kt | 24 +- .../loki/views/MentionCandidateView.kt | 10 +- .../loki/views/ProfilePictureView.kt | 6 +- .../securesms/loki/views/UserView.kt | 6 +- .../securesms/mms/PushMediaConstraints.java | 12 +- .../sskenvironment/ProfileManager.kt | 4 - .../database/MessageDataProvider.kt | 7 +- .../libsession/database/StorageProtocol.kt | 101 ++--- .../messaging/file_server/FileServerAPI.kt | 75 ---- .../messaging/file_server/FileServerAPIV2.kt | 26 +- .../messaging/jobs/AttachmentDownloadJob.kt | 11 +- .../messaging/jobs/AttachmentUploadJob.kt | 24 +- .../messaging/mentions/MentionsManager.kt | 8 +- .../messaging/messages/Destination.kt | 10 +- .../messages/control/ConfigurationMessage.kt | 5 +- .../messaging/open_groups/OpenGroup.kt | 37 -- .../messaging/open_groups/OpenGroupAPI.kt | 394 ------------------ .../messaging/open_groups/OpenGroupAPIV2.kt | 42 +- .../messaging/open_groups/OpenGroupMessage.kt | 247 ----------- .../sending_receiving/MessageSender.kt | 34 +- .../MessageSenderClosedGroupHandler.kt | 4 +- .../ReceivedMessageHandler.kt | 26 +- .../pollers/ClosedGroupPollerV2.kt | 2 +- .../pollers/OpenGroupPoller.kt | 232 ----------- .../pollers/OpenGroupPollerV2.kt | 2 +- .../messaging/utilities/DotNetAPI.kt | 269 ------------ .../libsession/snode/OnionRequestAPI.kt | 4 +- .../libsession/utilities/DownloadUtilities.kt | 78 +--- .../utilities/ProfilePictureUtilities.kt | 2 +- .../libsession/utilities/SSKEnvironment.kt | 1 - .../libsession/utilities/UploadResult.kt | 3 + .../database/LokiUserDatabaseProtocol.kt | 1 - 55 files changed, 248 insertions(+), 2092 deletions(-) delete mode 100644 libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerAPI.kt delete mode 100644 libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt delete mode 100644 libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPI.kt delete mode 100644 libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupMessage.kt delete mode 100644 libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt delete mode 100644 libsession/src/main/java/org/session/libsession/messaging/utilities/DotNetAPI.kt create mode 100644 libsession/src/main/java/org/session/libsession/utilities/UploadResult.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 3f463b55c8..d58916b71b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -22,19 +22,15 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Handler; import android.os.Looper; - import androidx.annotation.NonNull; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ProcessLifecycleOwner; import androidx.multidex.MultiDexApplication; - import org.conscrypt.Conscrypt; import org.session.libsession.avatars.AvatarHelper; import org.session.libsession.messaging.MessagingModuleConfiguration; -import org.session.libsession.messaging.file_server.FileServerAPI; 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.pollers.ClosedGroupPollerV2; import org.session.libsession.messaging.sending_receiving.pollers.Poller; @@ -174,9 +170,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc if (userPublicKey != null) { MentionsManager.Companion.configureIfNeeded(userPublicKey, userDB); } - setUpStorageAPIIfNeeded(); resubmitProfilePictureIfNeeded(); - updateOpenGroupProfilePicturesIfNeeded(); if (userPublicKey != null) { registerForFCMIfNeeded(false); } @@ -401,20 +395,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(base, TextSecurePreferences.getLanguage(base))); } - private static class ProviderInitializationException extends RuntimeException { - } - - // region Loki - public boolean setUpStorageAPIIfNeeded() { - String userPublicKey = TextSecurePreferences.getLocalNumber(this); - if (userPublicKey == null || !IdentityKeyUtil.hasIdentityKey(this)) { - return false; - } - byte[] userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(this).getPrivateKey().serialize(); - LokiAPIDatabaseProtocol apiDB = DatabaseFactory.getLokiAPIDatabase(this); - FileServerAPI.Companion.configure(userPublicKey, userPrivateKey, apiDB); - return true; - } + private static class ProviderInitializationException extends RuntimeException { } public void registerForFCMIfNeeded(final Boolean force) { if (firebaseInstanceIdJob != null && firebaseInstanceIdJob.isActive() && !force) return; @@ -490,19 +471,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc }); } - public void updateOpenGroupProfilePicturesIfNeeded() { - AsyncTask.execute(() -> { - byte[] profileKey = ProfileKeyUtil.getProfileKey(this); - String url = TextSecurePreferences.getProfilePictureURL(this); - Set servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers(); - for (String server : servers) { - if (profileKey != null) { - OpenGroupAPI.setProfilePicture(server, profileKey, url); - } - } - }); - } - public void clearAllData(boolean isMigratingToV2KeyPair) { String token = TextSecurePreferences.getFCMToken(this); if (token != null && !token.isEmpty()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/MessageDetailsActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MessageDetailsActivity.java index f61186144c..314653743c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MessageDetailsActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MessageDetailsActivity.java @@ -30,18 +30,15 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.loader.app.LoaderManager.LoaderCallbacks; import androidx.loader.content.Loader; - - import org.session.libsession.messaging.messages.visible.LinkPreview; import org.session.libsession.messaging.messages.visible.OpenGroupInvitation; import org.session.libsession.messaging.messages.visible.Quote; import org.session.libsession.messaging.messages.visible.VisibleMessage; -import org.session.libsession.messaging.open_groups.OpenGroup; +import org.session.libsession.messaging.open_groups.OpenGroupV2; import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.messaging.utilities.UpdateMessageData; import org.thoughtcrime.securesms.MessageDetailsRecipientAdapter.RecipientDeliveryStatus; @@ -264,7 +261,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity } toFrom.setText(toFromRes); long threadID = messageRecord.getThreadId(); - OpenGroup openGroup = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadID); + OpenGroupV2 openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadID); if (openGroup != null && messageRecord.isOutgoing()) { toFrom.setVisibility(View.GONE); separator.setVisibility(View.GONE); diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt index 8608426055..d017e770f4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -5,10 +5,9 @@ import android.text.TextUtils import com.google.protobuf.ByteString import org.greenrobot.eventbus.EventBus 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.utilities.Address -import org.session.libsession.messaging.utilities.DotNetAPI +import org.session.libsession.utilities.UploadResult import org.session.libsession.utilities.Util import org.session.libsignal.utilities.guava.Optional import org.session.libsignal.messages.SignalServiceAttachment @@ -104,11 +103,7 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) return smsDatabase.isOutgoingMessage(timestamp) || mmsDatabase.isOutgoingMessage(timestamp) } - override fun getOpenGroup(threadID: Long): OpenGroup? { - return null // TODO: Implement - } - - override fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) { + override fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult) { val database = DatabaseFactory.getAttachmentDatabase(context) val databaseAttachment = getDatabaseAttachment(attachmentId) ?: return val attachmentPointer = SignalServiceAttachmentPointer(uploadResult.id, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java b/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java index ce4f00c530..b556d20f55 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java @@ -13,24 +13,18 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; - import com.annimon.stream.Stream; 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.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; - import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.RecipientModifiedListener; import org.session.libsession.utilities.TextSecurePreferences; @@ -197,15 +191,11 @@ public class QuoteView extends FrameLayout implements RecipientModifiedListener boolean outgoing = messageType != MESSAGE_TYPE_INCOMING; boolean isOwnNumber = Util.isOwnNumber(getContext(), author.getAddress().serialize()); - String quoteeDisplayName = author.toShortString(); + String quoteeDisplayName; - long threadID = DatabaseFactory.getThreadDatabase(getContext()).getOrCreateThreadIdFor(conversationRecipient); String senderHexEncodedPublicKey = author.getAddress().serialize(); - OpenGroup publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadID); if (senderHexEncodedPublicKey.equalsIgnoreCase(TextSecurePreferences.getLocalNumber(getContext()))) { quoteeDisplayName = TextSecurePreferences.getProfileName(getContext()); - } else if (publicChat != null) { - quoteeDisplayName = DatabaseFactory.getLokiUserDatabase(getContext()).getServerDisplayName(publicChat.getId(), senderHexEncodedPublicKey); } else { quoteeDisplayName = DatabaseFactory.getLokiUserDatabase(getContext()).getDisplayName(senderHexEncodedPublicKey); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 22504aa80b..ddad4b628a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -77,9 +77,7 @@ import androidx.core.view.MenuItemCompat; import androidx.lifecycle.ViewModelProviders; import androidx.loader.app.LoaderManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager; - import com.annimon.stream.Stream; - import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; @@ -90,7 +88,6 @@ import org.session.libsession.messaging.messages.signal.OutgoingSecureMediaMessa import org.session.libsession.messaging.messages.signal.OutgoingTextMessage; import org.session.libsession.messaging.messages.visible.OpenGroupInvitation; import org.session.libsession.messaging.messages.visible.VisibleMessage; -import org.session.libsession.messaging.open_groups.OpenGroup; import org.session.libsession.messaging.open_groups.OpenGroupV2; import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.messaging.sending_receiving.attachments.Attachment; @@ -193,7 +190,6 @@ import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.PushCharacterCalculator; - import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -204,7 +200,6 @@ import java.util.Locale; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; - import kotlin.Unit; import network.loki.messenger.R; @@ -377,12 +372,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(threadId, this); - OpenGroup publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId); OpenGroupV2 openGroupV2 = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadId); - if (publicChat != null) { - // Request open group info update and handle the successful result in #onOpenGroupInfoUpdated(). - PublicChatInfoUpdateWorker.scheduleInstant(this, publicChat.getServer(), publicChat.getChannel()); - } else if (openGroupV2 != null) { + if (openGroupV2 != null) { PublicChatInfoUpdateWorker.scheduleInstant(this, openGroupV2.getServer(), openGroupV2.getRoom()); if (openGroupV2.getRoom().equals("session") || openGroupV2.getRoom().equals("oxen") || openGroupV2.getRoom().equals("lokinet") || openGroupV2.getRoom().equals("crypto")) { @@ -1419,13 +1410,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity @Subscribe(threadMode = ThreadMode.MAIN) public void onOpenGroupInfoUpdated(OpenGroupUtilities.GroupInfoUpdatedEvent event) { - OpenGroup publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId); OpenGroupV2 openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadId); - if (publicChat != null && - publicChat.getChannel() == event.getChannel() && - publicChat.getServer().equals(event.getUrl())) { - this.updateSubtitleTextView(); - } if (openGroup != null && openGroup.getRoom().equals(event.getRoom()) && openGroup.getServer().equals(event.getUrl())) { @@ -2380,13 +2365,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity muteIndicatorImageView.setVisibility(View.VISIBLE); 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")) { - OpenGroup publicChat = DatabaseFactory.getLokiThreadDatabase(this).getPublicChat(threadId); OpenGroupV2 openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadId); - if (publicChat != null) { - Integer userCount = DatabaseFactory.getLokiAPIDatabase(this).getUserCount(publicChat.getChannel(), publicChat.getServer()); - if (userCount == null) { userCount = 0; } - subtitleTextView.setText(userCount + " members"); - } else if (openGroup != null) { + if (openGroup != null) { Integer userCount = DatabaseFactory.getLokiAPIDatabase(this).getUserCount(openGroup.getRoom(),openGroup.getServer()); if (userCount == null) { userCount = 0; } subtitleTextView.setText(userCount + " members"); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 275cbba606..425160b45d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -54,17 +54,13 @@ import androidx.loader.content.Loader; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.OnScrollListener; - import com.annimon.stream.Stream; - import org.session.libsession.messaging.MessagingModuleConfiguration; import org.session.libsession.messaging.messages.control.DataExtractionNotification; import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage; import org.session.libsession.messaging.messages.signal.OutgoingTextMessage; import org.session.libsession.messaging.messages.visible.Quote; import org.session.libsession.messaging.messages.visible.VisibleMessage; -import org.session.libsession.messaging.open_groups.OpenGroup; -import org.session.libsession.messaging.open_groups.OpenGroupAPI; import org.session.libsession.messaging.open_groups.OpenGroupAPIV2; import org.session.libsession.messaging.open_groups.OpenGroupV2; import org.session.libsession.messaging.sending_receiving.MessageSender; @@ -398,9 +394,8 @@ public class ConversationFragment extends Fragment boolean isGroupChat = recipient.isGroupRecipient(); if (isGroupChat) { - OpenGroup publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId); OpenGroupV2 openGroupChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getOpenGroupChat(threadId); - boolean isPublicChat = (publicChat != null || openGroupChat != null); + boolean isPublicChat = (openGroupChat != null); int selectedMessageCount = messageRecords.size(); boolean areAllSentByUser = true; Set uniqueUserSet = new HashSet<>(); @@ -412,10 +407,7 @@ public class ConversationFragment extends Fragment menu.findItem(R.id.menu_context_reply).setVisible(selectedMessageCount == 1); String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(requireContext()); boolean userCanModerate = - (isPublicChat && - ((publicChat != null && OpenGroupAPI.isUserModerator(userHexEncodedPublicKey, publicChat.getChannel(), publicChat.getServer())) - || (openGroupChat != null && OpenGroupAPIV2.isUserModerator(userHexEncodedPublicKey, openGroupChat.getRoom(), openGroupChat.getServer()))) - ); + (isPublicChat && (OpenGroupAPIV2.isUserModerator(userHexEncodedPublicKey, openGroupChat.getRoom(), openGroupChat.getServer()))); boolean isDeleteOptionVisible = !isPublicChat || (areAllSentByUser || userCanModerate); // allow banning if moderating a public chat and only one user's messages are selected boolean isBanOptionVisible = isPublicChat && userCanModerate && !areAllSentByUser && uniqueUserSet.size() == 1; @@ -515,7 +507,6 @@ public class ConversationFragment extends Fragment builder.setMessage(getActivity().getResources().getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messagesCount, messagesCount)); builder.setCancelable(true); - OpenGroup publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId); OpenGroupV2 openGroupChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getOpenGroupChat(threadId); builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() { @@ -527,7 +518,7 @@ public class ConversationFragment extends Fragment { @Override protected Void doInBackground(MessageRecord... messageRecords) { - if (publicChat != null || openGroupChat != null) { + if (openGroupChat != null) { ArrayList serverIDs = new ArrayList<>(); ArrayList ignoredMessages = new ArrayList<>(); ArrayList failedMessages = new ArrayList<>(); @@ -541,29 +532,7 @@ public class ConversationFragment extends Fragment ignoredMessages.add(messageRecord.getId()); } } - if (publicChat != null) { - OpenGroupAPI - .deleteMessages(serverIDs, publicChat.getChannel(), publicChat.getServer(), isSentByUser) - .success(l -> { - for (MessageRecord messageRecord : messageRecords) { - Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id, !messageRecord.isMms()); - if (l.contains(serverID)) { - if (messageRecord.isMms()) { - DatabaseFactory.getMmsDatabase(getActivity()).delete(messageRecord.getId()); - } else { - DatabaseFactory.getSmsDatabase(getActivity()).deleteMessage(messageRecord.getId()); - } - } else if (!ignoredMessages.contains(serverID)) { - failedMessages.add(messageRecord.getId()); - Log.w("Loki", "Failed to delete message: " + messageRecord.getId() + "."); - } - } - return null; - }). fail(e -> { - Log.w("Loki", "Couldn't delete message due to error: " + e.toString() + "."); - return null; - }); - } else if (openGroupChat != null) { + if (openGroupChat != null) { for (Long serverId : serverIDs) { OpenGroupAPIV2 .deleteMessage(serverId, openGroupChat.getRoom(), openGroupChat.getServer()) @@ -617,7 +586,6 @@ public class ConversationFragment extends Fragment builder.setTitle(R.string.ConversationFragment_ban_selected_user); builder.setCancelable(true); - final OpenGroup publicChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getPublicChat(threadId); final OpenGroupV2 openGroupChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getOpenGroupChat(threadId); builder.setPositiveButton(R.string.ban, (dialog, which) -> { @@ -630,17 +598,7 @@ public class ConversationFragment extends Fragment @Override protected Void doInBackground(String... userPublicKeyParam) { String userPublicKey = userPublicKeyParam[0]; - if (publicChat != null) { - OpenGroupAPI - .ban(userPublicKey, publicChat.getServer()) - .success(l -> { - Log.d("Loki", "User banned"); - return Unit.INSTANCE; - }).fail(e -> { - Log.e("Loki", "Couldn't ban user due to error",e); - return null; - }); - } else if (openGroupChat != null) { + if (openGroupChat != null) { OpenGroupAPIV2 .ban(userPublicKey, openGroupChat.getRoom(), openGroupChat.getServer()) .success(l -> { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index 1454e94336..7467cdcc78 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -45,17 +45,12 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; - import androidx.annotation.DimenRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.annimon.stream.Stream; - import org.session.libsession.messaging.jobs.AttachmentDownloadJob; import org.session.libsession.messaging.jobs.JobQueue; -import org.session.libsession.messaging.open_groups.OpenGroup; -import org.session.libsession.messaging.open_groups.OpenGroupAPI; import org.session.libsession.messaging.open_groups.OpenGroupAPIV2; import org.session.libsession.messaging.open_groups.OpenGroupV2; import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress; @@ -760,10 +755,6 @@ public class ConversationItem extends LinearLayout String publicKey = recipient.getAddress().toString(); profilePictureView.setPublicKey(publicKey); String displayName = recipient.getName(); - OpenGroup openGroup = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID); - if (displayName == null && openGroup != null) { - displayName = DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(openGroup.getId(), publicKey); - } profilePictureView.setDisplayName(displayName); profilePictureView.setAdditionalPublicKey(null); profilePictureView.setRSSFeed(false); @@ -898,20 +889,7 @@ public class ConversationItem extends LinearLayout @SuppressLint("SetTextI18n") private void setGroupMessageStatus(MessageRecord messageRecord, Recipient recipient) { if (groupThread && !messageRecord.isOutgoing()) { - // Show custom display names for group chats String displayName = recipient.toShortString(); - try { - String serverId = GroupUtil.getDecodedGroupID(conversationRecipient.getAddress().serialize()); - String senderDisplayName = DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(serverId, recipient.getAddress().serialize()); - if (senderDisplayName != null) { - displayName = senderDisplayName; - } else { - // opengroupv2 format - displayName = OpenGroupUtilities.getDisplayName(recipient); - } - } catch (Exception e) { - // Do nothing - } this.groupSender.setText(displayName); @@ -952,12 +930,8 @@ public class ConversationItem extends LinearLayout profilePictureView.setVisibility(VISIBLE); int visibility = View.GONE; - OpenGroup publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(messageRecord.getThreadId()); OpenGroupV2 openGroupV2 = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(messageRecord.getThreadId()); - if (publicChat != null) { - boolean isModerator = OpenGroupAPI.isUserModerator(current.getRecipient().getAddress().toString(), publicChat.getChannel(), publicChat.getServer()); - visibility = isModerator ? View.VISIBLE : View.GONE; - } else if (openGroupV2 != null) { + if (openGroupV2 != null) { boolean isModerator = OpenGroupAPIV2.isUserModerator(current.getRecipient().getAddress().toString(), openGroupV2.getRoom(), openGroupV2.getServer()); visibility = isModerator ? View.VISIBLE : View.GONE; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 3b5b424ad0..f476c01121 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.database import android.content.Context import android.net.Uri -import okhttp3.HttpUrl import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job @@ -13,7 +12,6 @@ import org.session.libsession.messaging.messages.signal.* import org.session.libsession.messaging.messages.signal.IncomingTextMessage import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage -import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.messaging.open_groups.OpenGroupV2 import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment @@ -40,7 +38,6 @@ import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob import org.thoughtcrime.securesms.loki.api.OpenGroupManager import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol -import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities import org.thoughtcrime.securesms.loki.utilities.get import org.thoughtcrime.securesms.loki.utilities.getString import org.thoughtcrime.securesms.mms.PartAuthority @@ -72,13 +69,13 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return TextSecurePreferences.getProfilePictureURL(context) } - override fun setUserProfilePictureUrl(newProfilePicture: String) { + override fun setUserProfilePictureURL(newValue: String) { val ourRecipient = Address.fromSerialized(getUserPublicKey()!!).let { Recipient.from(context, it, false) } - TextSecurePreferences.setProfilePictureURL(context, newProfilePicture) - RetrieveProfileAvatarJob(ourRecipient, newProfilePicture) - ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(ourRecipient, newProfilePicture)) + TextSecurePreferences.setProfilePictureURL(context, newValue) + RetrieveProfileAvatarJob(ourRecipient, newValue) + ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(ourRecipient, newValue)) } override fun getProfileKeyForRecipient(recipientPublicKey: String): ByteArray? { @@ -107,15 +104,15 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return registrationID } - override fun persistAttachments(messageId: Long, attachments: List): List { + override fun persistAttachments(messageID: Long, attachments: List): List { val database = DatabaseFactory.getAttachmentDatabase(context) val databaseAttachments = attachments.mapNotNull { it.toSignalAttachment() } - return database.insertAttachments(messageId, databaseAttachments) + return database.insertAttachments(messageID, databaseAttachments) } - override fun getAttachmentsForMessage(messageId: Long): List { + override fun getAttachmentsForMessage(messageID: Long): List { val database = DatabaseFactory.getAttachmentDatabase(context) - return database.getAttachmentsForMessage(messageId) + return database.getAttachmentsForMessage(messageID) } override fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?, attachments: List): Long? { @@ -185,7 +182,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return messageID } - // JOBS override fun persistJob(job: Job) { DatabaseFactory.getSessionJobDatabase(context).persistJob(job) } @@ -219,20 +215,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return DatabaseFactory.getSessionJobDatabase(context).isJobCanceled(job) } - // Authorization - - override fun getAuthToken(server: String): String? { - return DatabaseFactory.getLokiAPIDatabase(context).getAuthToken(server) - } - - override fun setAuthToken(server: String, newValue: String?) { - DatabaseFactory.getLokiAPIDatabase(context).setAuthToken(server, newValue) - } - - override fun removeAuthToken(server: String) { - DatabaseFactory.getLokiAPIDatabase(context).setAuthToken(server, null) - } - override fun getAuthToken(room: String, server: String): String? { val id = "$server.$room" return DatabaseFactory.getLokiAPIDatabase(context).getAuthToken(id) @@ -248,30 +230,15 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseFactory.getLokiAPIDatabase(context).setAuthToken(id, null) } - override fun getOpenGroup(threadID: String): OpenGroup? { - if (threadID.toInt() < 0) { return null } - val database = databaseHelper.readableDatabase - return database.get(LokiThreadDatabase.publicChatTable, "${LokiThreadDatabase.threadID} = ?", arrayOf(threadID)) { cursor -> - val publicChatAsJSON = cursor.getString(LokiThreadDatabase.publicChat) - OpenGroup.fromJSON(publicChatAsJSON) - } - } - - override fun getV2OpenGroup(threadId: String): OpenGroupV2? { + override fun getV2OpenGroup(threadId: Long): OpenGroupV2? { if (threadId.toInt() < 0) { return null } val database = databaseHelper.readableDatabase - return database.get(LokiThreadDatabase.publicChatTable, "${LokiThreadDatabase.threadID} = ?", arrayOf(threadId)) { cursor -> + return database.get(LokiThreadDatabase.publicChatTable, "${LokiThreadDatabase.threadID} = ?", arrayOf( threadId.toString() )) { cursor -> val publicChatAsJson = cursor.getString(LokiThreadDatabase.publicChat) OpenGroupV2.fromJSON(publicChatAsJson) } } - override fun getThreadID(openGroupID: String): String { - val address = Address.fromSerialized(openGroupID) - val recipient = Recipient.from(context, address, false) - return DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient).toString() - } - override fun getOpenGroupPublicKey(server: String): String? { return DatabaseFactory.getLokiAPIDatabase(context).getOpenGroupPublicKey(server) } @@ -280,59 +247,27 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseFactory.getLokiAPIDatabase(context).setOpenGroupPublicKey(server, newValue) } - override fun setOpenGroupDisplayName(publicKey: String, channel: Long, server: String, displayName: String) { - val groupID = "$server.$channel" - DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(groupID, publicKey, displayName) - } - - override fun setOpenGroupDisplayName(publicKey: String, room: String, server: String, displayName: String) { - val groupID = "$server.$room" - DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(groupID, publicKey, displayName) - } - - override fun getOpenGroupDisplayName(publicKey: String, channel: Long, server: String): String? { - val groupID = "$server.$channel" - return DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(groupID, publicKey) - } - - override fun getOpenGroupDisplayName(publicKey: String, room: String, server: String): String? { - val groupID = "$server.$room" - return DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(groupID, publicKey) - } - - override fun getLastMessageServerId(room: String, server: String): Long? { + override fun getLastMessageServerID(room: String, server: String): Long? { return DatabaseFactory.getLokiAPIDatabase(context).getLastMessageServerID(room, server) } - override fun setLastMessageServerId(room: String, server: String, newValue: Long) { + override fun setLastMessageServerID(room: String, server: String, newValue: Long) { DatabaseFactory.getLokiAPIDatabase(context).setLastMessageServerID(room, server, newValue) } - override fun removeLastMessageServerId(room: String, server: String) { + override fun removeLastMessageServerID(room: String, server: String) { DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(room, server) } - override fun getLastMessageServerID(group: Long, server: String): Long? { - return DatabaseFactory.getLokiAPIDatabase(context).getLastMessageServerID(group, server) - } - - override fun setLastMessageServerID(group: Long, server: String, newValue: Long) { - DatabaseFactory.getLokiAPIDatabase(context).setLastMessageServerID(group, server, newValue) - } - - override fun removeLastMessageServerID(group: Long, server: String) { - DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(group, server) - } - - override fun getLastDeletionServerId(room: String, server: String): Long? { + override fun getLastDeletionServerID(room: String, server: String): Long? { return DatabaseFactory.getLokiAPIDatabase(context).getLastDeletionServerID(room, server) } - override fun setLastDeletionServerId(room: String, server: String, newValue: Long) { + override fun setLastDeletionServerID(room: String, server: String, newValue: Long) { DatabaseFactory.getLokiAPIDatabase(context).setLastDeletionServerID(room, server, newValue) } - override fun removeLastDeletionServerId(room: String, server: String) { + override fun removeLastDeletionServerID(room: String, server: String) { DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(room, server) } @@ -340,34 +275,15 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseFactory.getLokiAPIDatabase(context).setUserCount(room, server, newValue) } - override fun getLastDeletionServerID(group: Long, server: String): Long? { - return DatabaseFactory.getLokiAPIDatabase(context).getLastDeletionServerID(group, server) - } - - override fun setLastDeletionServerID(group: Long, server: String, newValue: Long) { - DatabaseFactory.getLokiAPIDatabase(context).setLastDeletionServerID(group, server, newValue) - } - - override fun removeLastDeletionServerID(group: Long, server: String) { - DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(group, server) + override fun setOpenGroupServerMessageID(messageID: Long, serverID: Long, threadID: Long, isSms: Boolean) { + DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageID, serverID, isSms) + DatabaseFactory.getLokiMessageDatabase(context).setOriginalThreadID(messageID, serverID, threadID) } override fun isDuplicateMessage(timestamp: Long): Boolean { return getReceivedMessageTimestamps().contains(timestamp) } - override fun setUserCount(group: Long, server: String, newValue: Int) { - DatabaseFactory.getLokiAPIDatabase(context).setUserCount(group, server, newValue) - } - - override fun setOpenGroupProfilePictureURL(group: Long, server: String, newValue: String) { - DatabaseFactory.getLokiAPIDatabase(context).setOpenGroupProfilePictureURL(group, server, newValue) - } - - override fun getOpenGroupProfilePictureURL(group: Long, server: String): String? { - return DatabaseFactory.getLokiAPIDatabase(context).getOpenGroupProfilePictureURL(group, server) - } - override fun updateTitle(groupID: String, newValue: String) { DatabaseFactory.getGroupDatabase(context).updateTitle(groupID, newValue) } @@ -394,15 +310,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return database.getMessageFor(timestamp, address)?.getId() } - override fun setOpenGroupServerMessageID(messageID: Long, serverID: Long, threadID: Long, isSms: Boolean) { - DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageID, serverID, isSms) - DatabaseFactory.getLokiMessageDatabase(context).setOriginalThreadID(messageID, serverID, threadID) - } - - override fun getQuoteServerID(quoteID: Long, publicKey: String): Long? { - return DatabaseFactory.getLokiMessageDatabase(context).getQuoteServerID(quoteID, publicKey) - } - override fun markAsSent(timestamp: Long, author: String) { val database = DatabaseFactory.getMmsSmsDatabase(context) val messageRecord = database.getMessageFor(timestamp, author) ?: return @@ -461,7 +368,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseFactory.getGroupDatabase(context).setActive(groupID, value) } - override fun getZombieMember(groupID: String): Set { + override fun getZombieMembers(groupID: String): Set { return DatabaseFactory.getGroupDatabase(context).getGroupZombieMembers(groupID).map { it.address.serialize() }.toHashSet() } @@ -473,7 +380,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseFactory.getGroupDatabase(context).updateMembers(groupID, members) } - override fun updateZombieMembers(groupID: String, members: List
) { + override fun setZombieMembers(groupID: String, members: List
) { DatabaseFactory.getGroupDatabase(context).updateZombieMembers(groupID, members) } @@ -539,39 +446,18 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseFactory.getLokiAPIDatabase(context).removeAllClosedGroupEncryptionKeyPairs(groupPublicKey) } - override fun getAllOpenGroups(): Map { - return DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().mapValues { (_,chat)-> - OpenGroup(chat.channel, chat.server, chat.displayName, chat.isDeletable) - } - } - override fun getAllV2OpenGroups(): Map { return DatabaseFactory.getLokiThreadDatabase(context).getAllV2OpenGroups() } - override fun addOpenGroup(serverUrl: String, channel: Long) { - val httpUrl = HttpUrl.parse(serverUrl) ?: return - if (httpUrl.queryParameterNames().contains("public_key")) { - // open group v2 - val server = HttpUrl.Builder().scheme(httpUrl.scheme()).host(httpUrl.host()).apply { - if (httpUrl.port() != 80 || httpUrl.port() != 443) { - // non-standard port, add to server - this.port(httpUrl.port()) - } - }.build() - val room = httpUrl.pathSegments().firstOrNull() ?: return - val publicKey = httpUrl.queryParameter("public_key") ?: return - - OpenGroupManager.add(server.toString().removeSuffix("/"), room, publicKey, context) - } else { - // TODO: No longer supported so let's remove this code - } - } - override fun getAllGroups(): List { return DatabaseFactory.getGroupDatabase(context).allGroups } + override fun addOpenGroup(urlAsString: String) { + OpenGroupManager.addOpenGroup(urlAsString, context) + } + override fun setProfileSharing(address: Address, value: Boolean) { val recipient = Recipient.from(context, address, false) DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, value) @@ -596,15 +482,19 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } } - override fun getThreadIdFor(address: Address): Long? { - val recipient = Recipient.from(context, address, false) - val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient) - return if (threadID < 0) null else threadID + override fun getThreadId(publicKeyOrOpenGroupID: String): Long? { + val address = Address.fromSerialized(publicKeyOrOpenGroupID) + return getThreadId(address) } - fun foo() { - val threadDB = DatabaseFactory.getThreadDatabase(context) + override fun getThreadId(address: Address): Long? { + val recipient = Recipient.from(context, address, false) + return getThreadId(recipient) + } + override fun getThreadId(recipient: Recipient): Long? { + val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient) + return if (threadID < 0) null else threadID } override fun getThreadIdForMms(mmsId: Long): Long { @@ -616,22 +506,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return threadId } - override fun getSessionRequestSentTimestamp(publicKey: String): Long? { - return DatabaseFactory.getLokiAPIDatabase(context).getSessionRequestSentTimestamp(publicKey) - } - - override fun setSessionRequestSentTimestamp(publicKey: String, newValue: Long) { - DatabaseFactory.getLokiAPIDatabase(context).setSessionRequestSentTimestamp(publicKey, newValue) - } - - override fun getSessionRequestProcessedTimestamp(publicKey: String): Long? { - return DatabaseFactory.getLokiAPIDatabase(context).getSessionRequestProcessedTimestamp(publicKey) - } - - override fun setSessionRequestProcessedTimestamp(publicKey: String, newValue: Long) { - DatabaseFactory.getLokiAPIDatabase(context).setSessionRequestProcessedTimestamp(publicKey, newValue) - } - override fun getDisplayName(publicKey: String): String? { return DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey) } @@ -640,10 +514,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseFactory.getLokiUserDatabase(context).setDisplayName(publicKey, newName) } - override fun getServerDisplayName(serverID: String, publicKey: String): String? { - return DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(serverID, publicKey) - } - override fun getProfilePictureURL(publicKey: String): String? { return DatabaseFactory.getLokiUserDatabase(context).getProfilePictureURL(publicKey) } @@ -691,7 +561,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return PartAuthority.getAttachmentThumbnailUri(attachmentId) } - // Data Extraction Notification override fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) { val database = DatabaseFactory.getMmsDatabase(context) val address = fromSerialized(senderPublicKey) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java index 1c7bf1142f..3cabdce5d7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java @@ -92,7 +92,7 @@ public class AvatarDownloadJob extends BaseJob implements InjectableType { SignalServiceAttachmentPointer pointer = new SignalServiceAttachmentPointer(avatarId, contentType, key, Optional.of(0), Optional.absent(), 0, 0, digest, fileName, false, Optional.absent(), url); if (pointer.getUrl().isEmpty()) throw new InvalidMessageException("Missing attachment URL."); - DownloadUtilities.downloadFile(attachment, pointer.getUrl(), MAX_AVATAR_SIZE, null); + DownloadUtilities.downloadFile(attachment, pointer.getUrl()); // Assume we're retrieving an attachment for an open group server if the digest is not set InputStream inputStream; diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java index 080ce6c88c..34a010000e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java @@ -100,7 +100,7 @@ public class RetrieveProfileAvatarJob extends BaseJob implements InjectableType File downloadDestination = File.createTempFile("avatar", ".jpg", context.getCacheDir()); try { - DownloadUtilities.downloadFile(downloadDestination, profileAvatar, MAX_PROFILE_SIZE_BYTES, null); + DownloadUtilities.downloadFile(downloadDestination, profileAvatar); InputStream avatarStream = new ProfileCipherInputStream(new FileInputStream(downloadDestination), profileKey); File decryptDestination = File.createTempFile("avatar", ".jpg", context.getCacheDir()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/BackupRestoreActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/BackupRestoreActivity.kt index 6450115184..a1bc90237b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/BackupRestoreActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/BackupRestoreActivity.kt @@ -28,7 +28,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import network.loki.messenger.R -import network.loki.messenger.databinding.ActivityBackupRestoreBinding import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.backup.FullBackupImporter @@ -61,31 +60,31 @@ class BackupRestoreActivity : BaseActionBarActivity() { super.onCreate(savedInstanceState) setUpActionBarSessionLogo() - val viewBinding = DataBindingUtil.setContentView(this, R.layout.activity_backup_restore) - viewBinding.lifecycleOwner = this - viewBinding.viewModel = viewModel +// val viewBinding = DataBindingUtil.setContentView(this, R.layout.activity_backup_restore) +// viewBinding.lifecycleOwner = this +// viewBinding.viewModel = viewModel - viewBinding.restoreButton.setOnClickListener { viewModel.tryRestoreBackup() } +// viewBinding.restoreButton.setOnClickListener { viewModel.tryRestoreBackup() } - viewBinding.buttonSelectFile.setOnClickListener { - fileSelectionResultLauncher.launch(Intent(Intent.ACTION_OPEN_DOCUMENT).apply { - //FIXME On some old APIs (tested on 21 & 23) the mime type doesn't filter properly - // and the backup files are unavailable for selection. -// type = BackupUtil.BACKUP_FILE_MIME_TYPE - type = "*/*" - }) - } +// viewBinding.buttonSelectFile.setOnClickListener { +// fileSelectionResultLauncher.launch(Intent(Intent.ACTION_OPEN_DOCUMENT).apply { +// //FIXME On some old APIs (tested on 21 & 23) the mime type doesn't filter properly +// // and the backup files are unavailable for selection. +//// type = BackupUtil.BACKUP_FILE_MIME_TYPE +// type = "*/*" +// }) +// } - viewBinding.backupCode.addTextChangedListener { text -> viewModel.backupPassphrase.value = text.toString() } +// viewBinding.backupCode.addTextChangedListener { text -> viewModel.backupPassphrase.value = text.toString() } // Focus passphrase text edit when backup file is selected. - viewModel.backupFile.observe(this, { backupFile -> - if (backupFile != null) viewBinding.backupCode.post { - viewBinding.backupCode.requestFocus() - (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager) - .showSoftInput(viewBinding.backupCode, InputMethodManager.SHOW_IMPLICIT) - } - }) +// viewModel.backupFile.observe(this, { backupFile -> +// if (backupFile != null) viewBinding.backupCode.post { +// viewBinding.backupCode.requestFocus() +// (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager) +// .showSoftInput(viewBinding.backupCode, InputMethodManager.SHOW_IMPLICIT) +// } +// }) // React to backup import result. viewModel.backupImportResult.observe(this) { result -> @@ -116,8 +115,8 @@ class BackupRestoreActivity : BaseActionBarActivity() { openURL("https://getsession.org/privacy-policy/") } }, 61, 75, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - viewBinding.termsTextView.movementMethod = LinkMovementMethod.getInstance() - viewBinding.termsTextView.text = termsExplanation +// viewBinding.termsTextView.movementMethod = LinkMovementMethod.getInstance() +// viewBinding.termsTextView.text = termsExplanation //endregion } @@ -190,7 +189,6 @@ class BackupRestoreViewModel(application: Application): AndroidViewModel(applica TextSecurePreferences.setHasViewedSeed(context, true) TextSecurePreferences.setHasSeenWelcomeScreen(context, true) val application = ApplicationContext.getInstance(context) - application.setUpStorageAPIIfNeeded() BackupRestoreResult.SUCCESS } catch (e: DatabaseDowngradeException) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt index 1b54c5010f..f8ed445146 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt @@ -30,7 +30,6 @@ import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import org.session.libsession.messaging.jobs.JobQueue 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.utilities.* import org.session.libsignal.utilities.toHexString @@ -332,16 +331,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), } } // Delete the conversation - val v1OpenGroup = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID) val v2OpenGroup = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(threadID) - if (v1OpenGroup != null) { - val apiDB = DatabaseFactory.getLokiAPIDatabase(context) - apiDB.removeLastMessageServerID(v1OpenGroup.channel, v1OpenGroup.server) - apiDB.removeLastDeletionServerID(v1OpenGroup.channel, v1OpenGroup.server) - apiDB.clearOpenGroupProfilePictureURL(v1OpenGroup.channel, v1OpenGroup.server) - OpenGroupAPI.leave(v1OpenGroup.channel, v1OpenGroup.server) - // FIXME: No longer supported so let's remove this code - } else if (v2OpenGroup != null) { + if (v2OpenGroup != null) { val apiDB = DatabaseFactory.getLokiAPIDatabase(context) apiDB.removeLastMessageServerID(v2OpenGroup.room, v2OpenGroup.server) apiDB.removeLastDeletionServerID(v2OpenGroup.room, v2OpenGroup.server) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt index 49662194e2..7e4210246b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt @@ -122,7 +122,6 @@ class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDel } // start polling and wait for updated message ApplicationContext.getInstance(this@LinkDeviceActivity).apply { - setUpStorageAPIIfNeeded() startPollingIfNeeded() } TextSecurePreferences.events.filter { it == TextSecurePreferences.CONFIGURATION_SYNCED }.collect { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/PNModeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/PNModeActivity.kt index d5ce931eab..fe29f58914 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/PNModeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/PNModeActivity.kt @@ -153,7 +153,6 @@ class PNModeActivity : BaseActionBarActivity() { } TextSecurePreferences.setIsUsingFCM(this, (selectedOptionView == fcmOptionView)) val application = ApplicationContext.getInstance(this) - application.setUpStorageAPIIfNeeded() application.startPollingIfNeeded() application.registerForFCMIfNeeded(true) val intent = Intent(this, HomeActivity::class.java) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt index c45cb8036c..c521c45c9e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt @@ -26,7 +26,6 @@ import nl.komponents.kovenant.all import nl.komponents.kovenant.ui.alwaysUi import nl.komponents.kovenant.ui.successUi import org.session.libsession.avatars.AvatarHelper -import org.session.libsession.messaging.open_groups.OpenGroupAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.ProfilePictureUtilities import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol @@ -179,8 +178,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { val promises = mutableListOf>() val displayName = displayNameToBeUploaded if (displayName != null) { - val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers() - promises.addAll(servers.map { OpenGroupAPI.setDisplayName(displayName, it) }) TextSecurePreferences.setProfileName(this, displayName) } val profilePicture = profilePictureToBeUploaded @@ -195,7 +192,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { TextSecurePreferences.setProfileAvatarId(this, SecureRandom().nextInt()) TextSecurePreferences.setLastProfilePictureUpload(this, Date().time) ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey) - ApplicationContext.getInstance(this).updateOpenGroupProfilePicturesIfNeeded() } if (profilePicture != null || displayName != null) { MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(this@SettingsActivity) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/OpenGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/OpenGroupManager.kt index 20bc37e553..e63e775d35 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/OpenGroupManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/OpenGroupManager.kt @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.loki.api import android.content.Context import android.graphics.Bitmap import androidx.annotation.WorkerThread +import okhttp3.HttpUrl import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.messaging.open_groups.OpenGroupV2 @@ -49,8 +50,8 @@ object OpenGroupManager { val existingOpenGroup = threadDB.getOpenGroupChat(threadID) if (existingOpenGroup != null) { return } // Clear any existing data if needed - storage.removeLastDeletionServerId(room, server) - storage.removeLastMessageServerId(room, server) + storage.removeLastDeletionServerID(room, server) + storage.removeLastMessageServerID(room, server) // Store the public key storage.setOpenGroupPublicKey(server,publicKey) // Get an auth token @@ -95,9 +96,22 @@ object OpenGroupManager { } // Delete ThreadUtils.queue { - storage.removeLastDeletionServerId(room, server) - storage.removeLastMessageServerId(room, server) + storage.removeLastDeletionServerID(room, server) + storage.removeLastMessageServerID(room, server) GroupManager.deleteGroup(groupID, context) // Must be invoked on a background thread } } + + fun addOpenGroup(urlAsString: String, context: Context) { + val url = HttpUrl.parse(urlAsString) ?: return + val builder = HttpUrl.Builder().scheme(url.scheme()).host(url.host()) + if (url.port() != 80 || url.port() != 443) { + // Non-standard port; add to server + builder.port(url.port()) + } + val server = builder.build() + val room = url.pathSegments().firstOrNull() ?: return + val publicKey = url.queryParameter("public_key") ?: return + add(server.toString().removeSuffix("/"), room, publicKey, context) + } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatInfoUpdateWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatInfoUpdateWorker.kt index 7880efbc55..db0e6c5e7f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatInfoUpdateWorker.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/PublicChatInfoUpdateWorker.kt @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.loki.api import android.content.Context import androidx.work.* -import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities @@ -57,36 +56,16 @@ class PublicChatInfoUpdateWorker(val context: Context, params: WorkerParameters) override fun doWork(): Result { val serverUrl = inputData.getString(DATA_KEY_SERVER_URL)!! - val channel = inputData.getLong(DATA_KEY_CHANNEL, -1) val room = inputData.getString(DATA_KEY_ROOM) - - val isOpenGroupV2 = !room.isNullOrEmpty() && channel == -1L - - if (!isOpenGroupV2) { - val publicChatId = OpenGroup.getId(channel, serverUrl) - - return try { - Log.v(TAG, "Updating open group info for $publicChatId.") - OpenGroupUtilities.updateGroupInfo(context, serverUrl, channel) - Log.v(TAG, "Open group info was successfully updated for $publicChatId.") - Result.success() - } catch (e: Exception) { - Log.e(TAG, "Failed to update open group info for $publicChatId", e) - Result.failure() - } - } else { - val openGroupId = "$serverUrl.$room" - - return try { - Log.v(TAG, "Updating open group info for $openGroupId.") - OpenGroupUtilities.updateGroupInfo(context, serverUrl, room!!) - Log.v(TAG, "Open group info was successfully updated for $openGroupId.") - Result.success() - } catch (e: Exception) { - Log.e(TAG, "Failed to update open group info for $openGroupId", e) - Result.failure() - } - + val openGroupId = "$serverUrl.$room" + return try { + Log.v(TAG, "Updating open group info for $openGroupId.") + OpenGroupUtilities.updateGroupInfo(context, serverUrl, room!!) + Log.v(TAG, "Open group info was successfully updated for $openGroupId.") + Result.success() + } catch (e: Exception) { + Log.e(TAG, "Failed to update open group info for $openGroupId", e) + Result.failure() } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt index c591160c32..b0e1cfe992 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt @@ -3,17 +3,13 @@ package org.thoughtcrime.securesms.loki.database import android.content.ContentValues import android.content.Context import android.database.Cursor -import org.session.libsession.messaging.open_groups.OpenGroup - import org.thoughtcrime.securesms.database.Database import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.loki.utilities.* - import org.session.libsession.messaging.open_groups.OpenGroupV2 import org.session.libsession.utilities.Address import org.session.libsession.utilities.recipients.Recipient - import org.session.libsignal.utilities.JsonUtil class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) { @@ -22,7 +18,6 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa private val sessionResetTable = "loki_thread_session_reset_database" val publicChatTable = "loki_public_chat_database" val threadID = "thread_id" - private val friendRequestStatus = "friend_request_status" private val sessionResetStatus = "session_reset_status" val publicChat = "public_chat" @JvmStatic @@ -37,28 +32,6 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa return DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient) } - fun getAllPublicChats(): Map { - val database = databaseHelper.readableDatabase - var cursor: Cursor? = null - val result = mutableMapOf() - try { - cursor = database.rawQuery("select * from $publicChatTable", null) - while (cursor != null && cursor.moveToNext()) { - val threadID = cursor.getLong(threadID) - val string = cursor.getString(publicChat) - val publicChat = OpenGroup.fromJSON(string) - if (publicChat != null) { - result[threadID] = publicChat - } - } - } catch (e: Exception) { - // Do nothing - } finally { - cursor?.close() - } - return result - } - fun getAllV2OpenGroups(): Map { val database = databaseHelper.readableDatabase var cursor: Cursor? = null @@ -79,20 +52,6 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa return result } - fun getAllPublicChatServers(): Set { - return getAllPublicChats().values.fold(setOf()) { set, chat -> set.plus(chat.server) } - } - - fun getPublicChat(threadID: Long): OpenGroup? { - if (threadID < 0) { return null } - - val database = databaseHelper.readableDatabase - return database.get(publicChatTable, "${Companion.threadID} = ?", arrayOf(threadID.toString())) { cursor -> - val publicChatAsJSON = cursor.getString(publicChat) - OpenGroup.fromJSON(publicChatAsJSON) - } - } - fun getOpenGroupChat(threadID: Long): OpenGroupV2? { if (threadID < 0) { return null @@ -114,19 +73,4 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa contentValues.put(publicChat, JsonUtil.toJson(openGroupV2.toJson())) database.insertOrUpdate(publicChatTable, contentValues, "${Companion.threadID} = ?", arrayOf(threadID.toString())) } - - fun setPublicChat(publicChat: OpenGroup, threadID: Long) { - if (threadID < 0) { - return - } - val database = databaseHelper.writableDatabase - val contentValues = ContentValues(2) - contentValues.put(Companion.threadID, threadID) - contentValues.put(Companion.publicChat, JsonUtil.toJson(publicChat.toJSON())) - database.insertOrUpdate(publicChatTable, contentValues, "${Companion.threadID} = ?", arrayOf(threadID.toString())) - } - - fun removePublicChat(threadID: Long) { - databaseHelper.writableDatabase.delete(publicChatTable, "${Companion.threadID} = ?", arrayOf(threadID.toString())) - } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiUserDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiUserDatabase.kt index 3323283514..1a7efd48ff 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiUserDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiUserDatabase.kt @@ -54,27 +54,6 @@ class LokiUserDatabase(context: Context, helper: SQLCipherOpenHelper) : Database Recipient.from(context, Address.fromSerialized(publicKey), false).notifyListeners() } - override fun getServerDisplayName(serverID: String, publicKey: String): String? { - val database = databaseHelper.readableDatabase - return database.get(serverDisplayNameTable, "${Companion.publicKey} = ? AND ${Companion.serverID} = ?", arrayOf( publicKey, serverID )) { cursor -> - cursor.getString(cursor.getColumnIndexOrThrow(displayName)) - } - } - - fun setServerDisplayName(serverID: String, publicKey: String, displayName: String) { - val database = databaseHelper.writableDatabase - val values = ContentValues(3) - values.put(Companion.serverID, serverID) - values.put(Companion.publicKey, publicKey) - values.put(Companion.displayName, displayName) - try { - database.insertWithOnConflict(serverDisplayNameTable, null, values, SQLiteDatabase.CONFLICT_REPLACE) - Recipient.from(context, Address.fromSerialized(publicKey), false).notifyListeners() - } catch (e: Exception) { - Log.d("Loki", "Couldn't save server display name due to exception: $e.") - } - } - override fun getProfilePictureURL(publicKey: String): String? { return if (publicKey == TextSecurePreferences.getLocalNumber(context)) { TextSecurePreferences.getProfilePictureURL(context) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/SessionMetaProtocol.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/SessionMetaProtocol.kt index c1145c5f49..b270cdecf6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/SessionMetaProtocol.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/SessionMetaProtocol.kt @@ -39,12 +39,6 @@ object SessionMetaProtocol { return shouldIgnoreMessage } - @JvmStatic - fun shouldIgnoreDecryptionException(context: Context, timestamp: Long): Boolean { - val restorationTimestamp = TextSecurePreferences.getRestorationTime(context) - return timestamp <= restorationTimestamp - } - @JvmStatic fun handleProfileUpdateIfNeeded(context: Context, content: SignalServiceContent) { val displayName = content.senderDisplayName.orNull() ?: return @@ -58,24 +52,6 @@ object SessionMetaProtocol { DatabaseFactory.getLokiUserDatabase(context).setDisplayName(sender, displayName) } - @JvmStatic - fun handleProfileKeyUpdate(context: Context, content: SignalServiceContent) { - val message = content.dataMessage.get() - if (!message.profileKey.isPresent) { return } - val database = DatabaseFactory.getRecipientDatabase(context) - val recipient = Recipient.from(context, Address.fromSerialized(content.sender), false) - if (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey, message.profileKey.get())) { - database.setProfileKey(recipient, message.profileKey.get()) - database.setUnidentifiedAccessMode(recipient, Recipient.UnidentifiedAccessMode.UNKNOWN) - val url = content.senderProfilePictureURL.or("") - ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(recipient, url)) - val userPublicKey = TextSecurePreferences.getLocalNumber(context) - if (userPublicKey == content.sender) { - ApplicationContext.getInstance(context).updateOpenGroupProfilePicturesIfNeeded() - } - } - } - @JvmStatic fun canUserReplyToNotification(recipient: Recipient): Boolean { // TODO return !recipient.address.isRSSFeed diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionUtilities.kt index 50b69274da..300d5466a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/MentionUtilities.kt @@ -27,15 +27,12 @@ object MentionUtilities { var matcher = pattern.matcher(text) val mentions = mutableListOf, String>>() var startIndex = 0 - val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID) val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! if (matcher.find(startIndex)) { while (true) { val publicKey = text.subSequence(matcher.start() + 1, matcher.end()).toString() // +1 to get rid of the @ val userDisplayName: String? = if (publicKey.toLowerCase() == userPublicKey.toLowerCase()) { TextSecurePreferences.getProfileName(context) - } else if (publicChat != null) { - DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, publicKey) } else { DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/NotificationUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/NotificationUtilities.kt index 7db784d8ca..ad3d35f809 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/NotificationUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/NotificationUtilities.kt @@ -6,13 +6,8 @@ import org.thoughtcrime.securesms.database.DatabaseFactory import org.session.libsession.utilities.recipients.Recipient fun getOpenGroupDisplayName(recipient: Recipient, threadRecipient: Recipient, context: Context): String { - val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(threadRecipient) - val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID) val publicKey = recipient.address.toString() - val displayName = if (publicChat != null) { - DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, publicKey) - } else { - DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey) - } + val displayName = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey) + // FIXME: Add short ID here? return displayName ?: publicKey } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt index 8c08d53e3b..2f562e87c6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/OpenGroupUtilities.kt @@ -3,18 +3,10 @@ package org.thoughtcrime.securesms.loki.utilities import android.content.Context import androidx.annotation.WorkerThread import org.greenrobot.eventbus.EventBus -import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.open_groups.OpenGroup -import org.session.libsession.messaging.open_groups.OpenGroupAPI import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 -import org.session.libsession.messaging.open_groups.OpenGroupV2 import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.GroupUtil -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.ProfileKeyUtil -import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.DatabaseFactory -import org.thoughtcrime.securesms.groups.GroupManager import java.util.* //TODO Refactor so methods declare specific type of checked exceptions and not generalized Exception. @@ -28,23 +20,6 @@ object OpenGroupUtilities { * * Consider using [org.thoughtcrime.securesms.loki.api.PublicChatInfoUpdateWorker] for lazy approach. */ - @JvmStatic - @WorkerThread - @Throws(Exception::class) - fun updateGroupInfo(context: Context, url: String, channel: Long) { - // Check if open group has a related DB record. - val groupId = GroupUtil.getEncodedOpenGroupID(OpenGroup.getId(channel, url).toByteArray()) - if (!DatabaseFactory.getGroupDatabase(context).hasGroup(groupId)) { - throw IllegalStateException("Attempt to update open group info for non-existent DB record: $groupId") - } - - val info = OpenGroupAPI.getChannelInfo(channel, url).get() - - OpenGroupAPI.updateProfileIfNeeded(channel, url, groupId, info, false) - - EventBus.getDefault().post(GroupInfoUpdatedEvent(url, channel)) - } - @JvmStatic @WorkerThread @Throws(Exception::class) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateSelectionView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateSelectionView.kt index d702610668..b7745a67a1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateSelectionView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateSelectionView.kt @@ -17,10 +17,10 @@ class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defS set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.mentionCandidates = newValue } var glide: GlideRequests? = null set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.glide = newValue } - var publicChatServer: String? = null - set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.publicChatServer = publicChatServer } - var publicChatChannel: Long? = null - set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.publicChatChannel = publicChatChannel } + var openGroupServer: String? = null + set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.openGroupServer = openGroupServer } + var openGroupRoom: String? = null + set(newValue) { field = newValue; mentionCandidateSelectionViewAdapter.openGroupRoom = openGroupRoom } var onMentionCandidateSelected: ((Mention) -> Unit)? = null private val mentionCandidateSelectionViewAdapter by lazy { Adapter(context) } @@ -29,8 +29,8 @@ class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defS var mentionCandidates = listOf() set(newValue) { field = newValue; notifyDataSetChanged() } var glide: GlideRequests? = null - var publicChatServer: String? = null - var publicChatChannel: Long? = null + var openGroupServer: String? = null + var openGroupRoom: String? = null override fun getCount(): Int { return mentionCandidates.count() @@ -49,8 +49,8 @@ class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defS val mentionCandidate = getItem(position) cell.glide = glide cell.mentionCandidate = mentionCandidate - cell.publicChatServer = publicChatServer - cell.publicChatChannel = publicChatChannel + cell.openGroupServer = openGroupServer + cell.openGroupRoom = openGroupRoom return cell } } @@ -68,10 +68,10 @@ class MentionCandidateSelectionView(context: Context, attrs: AttributeSet?, defS } fun show(mentionCandidates: List, threadID: Long) { - val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID) - if (publicChat != null) { - publicChatServer = publicChat.server - publicChatChannel = publicChat.channel + val openGroup = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(threadID) + if (openGroup != null) { + openGroupServer = openGroup.server + openGroupRoom = openGroup.room } this.mentionCandidates = mentionCandidates val layoutParams = this.layoutParams as ViewGroup.LayoutParams diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt index 07233c3e06..0a748c98f7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt @@ -8,16 +8,16 @@ import android.view.ViewGroup import android.widget.LinearLayout import kotlinx.android.synthetic.main.view_mention_candidate.view.* import network.loki.messenger.R -import org.session.libsession.messaging.open_groups.OpenGroupAPI import org.session.libsession.messaging.mentions.Mention +import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.thoughtcrime.securesms.mms.GlideRequests class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) { var mentionCandidate = Mention("", "") set(newValue) { field = newValue; update() } var glide: GlideRequests? = null - var publicChatServer: String? = null - var publicChatChannel: Long? = null + var openGroupServer: String? = null + var openGroupRoom: String? = null constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) constructor(context: Context) : this(context, null) @@ -37,8 +37,8 @@ class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr: profilePictureView.isRSSFeed = false profilePictureView.glide = glide!! profilePictureView.update() - if (publicChatServer != null && publicChatChannel != null) { - val isUserModerator = OpenGroupAPI.isUserModerator(mentionCandidate.publicKey, publicChatChannel!!, publicChatServer!!) + if (openGroupServer != null && openGroupRoom != null) { + val isUserModerator = OpenGroupAPIV2.isUserModerator(mentionCandidate.publicKey, openGroupRoom!!, openGroupServer!!) moderatorIconImageView.visibility = if (isUserModerator) View.VISIBLE else View.GONE } else { moderatorIconImageView.visibility = View.GONE diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt index fbc469379e..69bed456ad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt @@ -61,11 +61,7 @@ class ProfilePictureView : RelativeLayout { if (publicKey == null || publicKey.isBlank()) { return null } else { - var result = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey) - val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID) - if (result == null && publicChat != null) { - result = DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, publicKey) - } + val result = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey) return result ?: publicKey } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/UserView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/UserView.kt index 193bd1bd29..4e35ba1869 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/UserView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/UserView.kt @@ -52,11 +52,7 @@ class UserView : LinearLayout { if (publicKey == null || publicKey.isBlank()) { return null } else { - var result = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey) - val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(openGroupThreadID) - if (result == null && publicChat != null) { - result = DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, publicKey) - } + val result = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey) return result ?: publicKey } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/PushMediaConstraints.java b/app/src/main/java/org/thoughtcrime/securesms/mms/PushMediaConstraints.java index 12fc8279f0..f71050aba9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/PushMediaConstraints.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/PushMediaConstraints.java @@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.mms; import android.content.Context; -import org.session.libsession.messaging.file_server.FileServerAPI; +import org.session.libsession.messaging.file_server.FileServerAPIV2; public class PushMediaConstraints extends MediaConstraints { @@ -21,26 +21,26 @@ public class PushMediaConstraints extends MediaConstraints { @Override public int getImageMaxSize(Context context) { - return (int) (((double) FileServerAPI.Companion.getMaxFileSize()) / FileServerAPI.Companion.getFileSizeORMultiplier()); + return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier); } @Override public int getGifMaxSize(Context context) { - return (int) (((double) FileServerAPI.Companion.getMaxFileSize()) / FileServerAPI.Companion.getFileSizeORMultiplier()); + return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier); } @Override public int getVideoMaxSize(Context context) { - return (int) (((double) FileServerAPI.Companion.getMaxFileSize()) / FileServerAPI.Companion.getFileSizeORMultiplier()); + return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier); } @Override public int getAudioMaxSize(Context context) { - return (int) (((double) FileServerAPI.Companion.getMaxFileSize()) / FileServerAPI.Companion.getFileSizeORMultiplier()); + return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier); } @Override public int getDocumentMaxSize(Context context) { - return (int) (((double) FileServerAPI.Companion.getMaxFileSize()) / FileServerAPI.Companion.getFileSizeORMultiplier()); + return (int) (((double) FileServerAPIV2.maxFileSize) / FileServerAPIV2.fileSizeORMultiplier); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index ae98e00cbb..7bb7970ea6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -25,8 +25,4 @@ class ProfileManager: SSKEnvironment.ProfileManagerProtocol { val database = DatabaseFactory.getRecipientDatabase(context) database.setUnidentifiedAccessMode(recipient, unidentifiedAccessMode) } - - override fun updateOpenGroupProfilePicturesIfNeeded(context: Context) { - ApplicationContext.getInstance(context).updateOpenGroupProfilePicturesIfNeeded() - } } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt index 4650afc528..4e29585c7f 100644 --- a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt +++ b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt @@ -1,9 +1,8 @@ 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.utilities.Address -import org.session.libsession.messaging.utilities.DotNetAPI +import org.session.libsession.utilities.UploadResult import org.session.libsignal.messages.SignalServiceAttachmentPointer import org.session.libsignal.messages.SignalServiceAttachmentStream import java.io.InputStream @@ -29,7 +28,7 @@ interface MessageDataProvider { fun isOutgoingMessage(timestamp: Long): Boolean - fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) + fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult) fun handleFailedAttachmentUpload(attachmentId: Long) fun getMessageForQuote(timestamp: Long, author: Address): Pair? @@ -38,6 +37,4 @@ interface MessageDataProvider { fun getAttachmentIDsFor(messageID: Long): List fun getLinkPreviewAttachmentIDFor(messageID: Long): Long? - - fun getOpenGroup(threadID: Long): OpenGroup? } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index dfb774e94f..13ae5395d8 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -1,6 +1,5 @@ package org.session.libsession.database - import android.content.Context import android.net.Uri import org.session.libsession.messaging.jobs.AttachmentUploadJob @@ -9,7 +8,6 @@ import org.session.libsession.messaging.jobs.MessageSendJob import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage -import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.messaging.open_groups.OpenGroupV2 import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage @@ -18,6 +16,7 @@ import org.session.libsession.messaging.sending_receiving.link_preview.LinkPrevi import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupRecord +import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient.RecipientSettings import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.messages.SignalServiceAttachmentPointer @@ -32,14 +31,12 @@ interface StorageProtocol { fun getUserDisplayName(): String? fun getUserProfileKey(): ByteArray? fun getUserProfilePictureURL(): String? - fun setUserProfilePictureUrl(newProfilePicture: String) - + fun setUserProfilePictureURL(newValue: String) fun getProfileKeyForRecipient(recipientPublicKey: String): ByteArray? fun getDisplayNameForRecipient(recipientPublicKey: String): String? fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray) - // Signal Protocol - + // Signal fun getOrGenerateRegistrationID(): Int // Jobs @@ -59,48 +56,40 @@ interface StorageProtocol { // Open Groups fun getAllV2OpenGroups(): Map - fun getV2OpenGroup(threadId: String): OpenGroupV2? - - // Open Groups - fun getThreadID(openGroupID: String): String? - fun addOpenGroup(serverUrl: String, channel: Long) + fun getV2OpenGroup(threadId: Long): OpenGroupV2? + fun addOpenGroup(urlAsString: String) fun setOpenGroupServerMessageID(messageID: Long, serverID: Long, threadID: Long, isSms: Boolean) - fun getQuoteServerID(quoteID: Long, publicKey: String): Long? // Open Group Public Keys fun getOpenGroupPublicKey(server: String): String? fun setOpenGroupPublicKey(server: String, newValue: String) - // Open Group User Info - fun setOpenGroupDisplayName(publicKey: String, room: String, server: String, displayName: String) - fun getOpenGroupDisplayName(publicKey: String, room: String, server: String): String? - // Open Group Metadata - fun updateTitle(groupID: String, newValue: String) fun updateProfilePicture(groupID: String, newValue: ByteArray) fun setUserCount(room: String, server: String, newValue: Int) // Last Message Server ID - fun getLastMessageServerId(room: String, server: String): Long? - fun setLastMessageServerId(room: String, server: String, newValue: Long) - fun removeLastMessageServerId(room: String, server: String) + fun getLastMessageServerID(room: String, server: String): Long? + fun setLastMessageServerID(room: String, server: String, newValue: Long) + fun removeLastMessageServerID(room: String, server: String) // Last Deletion Server ID - fun getLastDeletionServerId(room: String, server: String): Long? - fun setLastDeletionServerId(room: String, server: String, newValue: Long) - fun removeLastDeletionServerId(room: String, server: String) + fun getLastDeletionServerID(room: String, server: String): Long? + fun setLastDeletionServerID(room: String, server: String, newValue: Long) + fun removeLastDeletionServerID(room: String, server: String) // Message Handling fun isDuplicateMessage(timestamp: Long): Boolean fun getReceivedMessageTimestamps(): Set fun addReceivedMessageTimestamp(timestamp: Long) fun removeReceivedMessageTimestamps(timestamps: Set) - // Returns the IDs of the saved attachments. - fun persistAttachments(messageId: Long, attachments: List): List - fun getAttachmentsForMessage(messageId: Long): List - - fun getMessageIdInDatabase(timestamp: Long, author: String): Long? + /** + * Returns the IDs of the saved attachments. + */ + fun persistAttachments(messageID: Long, attachments: List): List + fun getAttachmentsForMessage(messageID: Long): List + fun getMessageIdInDatabase(timestamp: Long, author: String): Long? // TODO: This is a weird name fun markAsSent(timestamp: Long, author: String) fun markUnidentified(timestamp: Long, author: String) fun setErrorMessage(timestamp: Long, author: String, error: Exception) @@ -110,11 +99,10 @@ interface StorageProtocol { fun createGroup(groupID: String, title: String?, members: List
, avatar: SignalServiceAttachmentPointer?, relay: String?, admins: List
, formationTimestamp: Long) fun isGroupActive(groupPublicKey: String): Boolean fun setActive(groupID: String, value: Boolean) - fun getZombieMember(groupID: String): Set + fun getZombieMembers(groupID: String): Set fun removeMember(groupID: String, member: Address) fun updateMembers(groupID: String, members: List
) - fun updateZombieMembers(groupID: String, members: List
) - // Closed Group + fun setZombieMembers(groupID: String, members: List
) fun getAllClosedGroupPublicKeys(): Set fun getAllActiveClosedGroupPublicKeys(): Set fun addClosedGroupPublicKey(groupPublicKey: String) @@ -122,9 +110,9 @@ interface StorageProtocol { fun addClosedGroupEncryptionKeyPair(encryptionKeyPair: ECKeyPair, groupPublicKey: String) fun removeAllClosedGroupEncryptionKeyPairs(groupPublicKey: String) fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type, - name: String, members: Collection, admins: Collection, sentTimestamp: Long) + name: String, members: Collection, admins: Collection, sentTimestamp: Long) fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, - members: Collection, admins: Collection, threadID: Long, sentTimestamp: Long) + members: Collection, admins: Collection, threadID: Long, sentTimestamp: Long) fun isClosedGroup(publicKey: String): Boolean fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList fun getLatestClosedGroupEncryptionKeyPair(groupPublicKey: String): ECKeyPair? @@ -138,58 +126,27 @@ interface StorageProtocol { // Thread fun getOrCreateThreadIdFor(address: Address): Long fun getOrCreateThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?): Long - fun getThreadIdFor(address: Address): Long? + fun getThreadId(publicKeyOrOpenGroupID: String): Long? + fun getThreadId(address: Address): Long? + fun getThreadId(recipient: Recipient): Long? fun getThreadIdForMms(mmsId: Long): Long fun getLastUpdated(threadID: Long): Long - // Session Request - fun getSessionRequestSentTimestamp(publicKey: String): Long? - fun setSessionRequestSentTimestamp(publicKey: String, newValue: Long) - fun getSessionRequestProcessedTimestamp(publicKey: String): Long? - fun setSessionRequestProcessedTimestamp(publicKey: String, newValue: Long) - - // Loki User + // Contacts fun getDisplayName(publicKey: String): String? fun setDisplayName(publicKey: String, newName: String) - fun getServerDisplayName(serverID: String, publicKey: String): String? fun getProfilePictureURL(publicKey: String): String? - - // Recipient fun getRecipientSettings(address: Address): RecipientSettings? fun addContacts(contacts: List) - // PartAuthority + // Attachments fun getAttachmentDataUri(attachmentId: AttachmentId): Uri fun getAttachmentThumbnailUri(attachmentId: AttachmentId): Uri // Message Handling - /// Returns the ID of the `TSIncomingMessage` that was constructed. + /** + * Returns the ID of the `TSIncomingMessage` that was constructed. + */ fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?, attachments: List): Long? - - // Data Extraction Notification fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) - - // DEPRECATED - fun getAuthToken(server: String): String? - fun setAuthToken(server: String, newValue: String?) - fun removeAuthToken(server: String) - - fun getLastMessageServerID(group: Long, server: String): Long? - fun setLastMessageServerID(group: Long, server: String, newValue: Long) - fun removeLastMessageServerID(group: Long, server: String) - - fun getLastDeletionServerID(group: Long, server: String): Long? - fun setLastDeletionServerID(group: Long, server: String, newValue: Long) - fun removeLastDeletionServerID(group: Long, server: String) - - fun getOpenGroup(threadID: String): OpenGroup? - fun getAllOpenGroups(): Map - - fun setUserCount(group: Long, server: String, newValue: Int) - fun setOpenGroupProfilePictureURL(group: Long, server: String, newValue: String) - fun getOpenGroupProfilePictureURL(group: Long, server: String): String? - - fun setOpenGroupDisplayName(publicKey: String, channel: Long, server: String, displayName: String) - fun getOpenGroupDisplayName(publicKey: String, channel: Long, server: String): String? - } diff --git a/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerAPI.kt deleted file mode 100644 index aeca8b2562..0000000000 --- a/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerAPI.kt +++ /dev/null @@ -1,75 +0,0 @@ -package org.session.libsession.messaging.file_server - -import nl.komponents.kovenant.Promise -import nl.komponents.kovenant.functional.map -import okhttp3.Request -import org.session.libsession.messaging.utilities.DotNetAPI -import org.session.libsession.snode.OnionRequestAPI -import org.session.libsignal.utilities.Log -import org.session.libsignal.utilities.Base64 -import org.session.libsignal.utilities.JsonUtil -import org.session.libsignal.database.LokiAPIDatabaseProtocol -import org.session.libsignal.utilities.* -import java.net.URL - -class FileServerAPI(public val server: String, userPublicKey: String, userPrivateKey: ByteArray, private val database: LokiAPIDatabaseProtocol) : DotNetAPI() { - - companion object { - internal val fileServerPublicKey = "62509D59BDEEC404DD0D489C1E15BA8F94FD3D619B01C1BF48A9922BFCB7311C" - internal val maxRetryCount = 4 - - public val maxFileSize = 10_000_000 // 10 MB - /** - * The file server has a file size limit of `maxFileSize`, which the Service Nodes try to enforce as well. However, the limit applied by the Service Nodes - * is on the **HTTP request** and not the actual file size. Because the file server expects the file data to be base 64 encoded, the size of the HTTP - * request for a given file will be at least `ceil(n / 3) * 4` bytes, where n is the file size in bytes. This is the minimum size because there might also - * be other parameters in the request. On average the multiplier appears to be about 1.5, so when checking whether the file will exceed the file size limit when - * uploading a file we just divide the size of the file by this number. The alternative would be to actually check the size of the HTTP request but that's only - * possible after proof of work has been calculated and the onion request encryption has happened, which takes several seconds. - */ - public val fileSizeORMultiplier = 2 // TODO: It should be possible to set this to 1.5? - public val fileStorageBucketURL = "https://file-static.lokinet.org" - // endregion - - // region Initialization - lateinit var shared: FileServerAPI - - /** - * Must be called before `LokiAPI` is used. - */ - fun configure(userPublicKey: String, userPrivateKey: ByteArray, database: LokiAPIDatabaseProtocol) { - if (Companion::shared.isInitialized) { return } - val server = "https://file.getsession.org" - shared = FileServerAPI(server, userPublicKey, userPrivateKey, database) - } - // endregion - } - - // region Open Group Server Public Key - fun getPublicKeyForOpenGroupServer(openGroupServer: String): Promise { - val publicKey = database.getOpenGroupPublicKey(openGroupServer) - if (publicKey != null && PublicKeyValidation.isValid(publicKey, 64, false)) { - return Promise.of(publicKey) - } else { - val url = "$server/loki/v1/getOpenGroupKey/${URL(openGroupServer).host}" - val request = Request.Builder().url(url) - request.addHeader("Content-Type", "application/json") - request.addHeader("Authorization", "Bearer loki") // Tokenless request; use a dummy token - return OnionRequestAPI.sendOnionRequest(request.build(), server, fileServerPublicKey).map { json -> - try { - val bodyAsString = json["data"] as String - val body = JsonUtil.fromJson(bodyAsString) - val base64EncodedPublicKey = body.get("data").asText() - val prefixedPublicKey = Base64.decode(base64EncodedPublicKey) - val hexEncodedPrefixedPublicKey = prefixedPublicKey.toHexString() - val result = hexEncodedPrefixedPublicKey.removing05PrefixIfNeeded() - database.setOpenGroupPublicKey(openGroupServer, result) - result - } catch (exception: Exception) { - Log.d("Loki", "Couldn't parse open group public key from: $json.") - throw exception - } - } - } - } -} diff --git a/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerAPIV2.kt b/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerAPIV2.kt index 57cfbb6534..c229aab4be 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerAPIV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerAPIV2.kt @@ -15,10 +15,18 @@ import org.session.libsignal.utilities.Log object FileServerAPIV2 { - private const val OLD_SERVER_PUBLIC_KEY = "7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69" - const val OLD_SERVER = "http://88.99.175.227" - private const val SERVER_PUBLIC_KEY = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59" - const val SERVER = "http://filev2.getsession.org" + private const val serverPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59" + const val server = "http://filev2.getsession.org" + const val maxFileSize = 10_000_000 // 10 MB + /** + * The file server has a file size limit of `maxFileSize`, which the Service Nodes try to enforce as well. However, the limit applied by the Service Nodes + * is on the **HTTP request** and not the actual file size. Because the file server expects the file data to be base 64 encoded, the size of the HTTP + * request for a given file will be at least `ceil(n / 3) * 4` bytes, where n is the file size in bytes. This is the minimum size because there might also + * be other parameters in the request. On average the multiplier appears to be about 1.5, so when checking whether the file will exceed the file size limit when + * uploading a file we just divide the size of the file by this number. The alternative would be to actually check the size of the HTTP request but that's only + * possible after proof of work has been calculated and the onion request encryption has happened, which takes several seconds. + */ + const val fileSizeORMultiplier = 2 // TODO: It should be possible to set this to 1.5? sealed class Error(message: String) : Exception(message) { object ParsingFailed : Error("Invalid response.") @@ -44,9 +52,7 @@ object FileServerAPIV2 { return RequestBody.create(MediaType.get("application/json"), parametersAsJSON) } - private fun send(request: Request, useOldServer: Boolean): Promise, Exception> { - val server = if (useOldServer) OLD_SERVER else SERVER - val serverPublicKey = if (useOldServer) OLD_SERVER_PUBLIC_KEY else SERVER_PUBLIC_KEY + private fun send(request: Request): Promise, Exception> { val url = HttpUrl.parse(server) ?: return Promise.ofFail(OpenGroupAPIV2.Error.InvalidURL) val urlBuilder = HttpUrl.Builder() .scheme(url.scheme()) @@ -80,14 +86,14 @@ object FileServerAPIV2 { val base64EncodedFile = Base64.encodeBytes(file) val parameters = mapOf( "file" to base64EncodedFile ) val request = Request(verb = HTTP.Verb.POST, endpoint = "files", parameters = parameters) - return send(request, false).map { json -> + return send(request).map { json -> json["result"] as? Long ?: throw OpenGroupAPIV2.Error.ParsingFailed } } - fun download(file: Long, useOldServer: Boolean): Promise { + fun download(file: Long): Promise { val request = Request(verb = HTTP.Verb.GET, endpoint = "files/$file") - return send(request, useOldServer).map { json -> + return send(request).map { json -> val base64EncodedFile = json["result"] as? String ?: throw Error.ParsingFailed Base64.decode(base64EncodedFile) ?: throw Error.ParsingFailed } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt index 362b2e81ed..b0d923b0c3 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt @@ -2,11 +2,9 @@ package org.session.libsession.messaging.jobs import okhttp3.HttpUrl import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.file_server.FileServerAPI import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState import org.session.libsession.messaging.utilities.Data -import org.session.libsession.messaging.utilities.DotNetAPI import org.session.libsession.utilities.DownloadUtilities import org.session.libsignal.streams.AttachmentCipherInputStream import org.session.libsignal.utilities.Base64 @@ -42,11 +40,6 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) if (exception == Error.NoAttachment) { messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) this.handlePermanentFailure(exception) - } else if (exception == DotNetAPI.Error.ParsingFailed) { - // No need to retry if the response is invalid. Most likely this means we (incorrectly) - // got a "Cannot GET ..." error from the file server. - messageDataProvider.setAttachmentState(AttachmentState.FAILED, attachmentID, databaseMessageID) - this.handlePermanentFailure(exception) } else { this.handleFailure(exception) } @@ -57,9 +50,9 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) messageDataProvider.setAttachmentState(AttachmentState.STARTED, attachmentID, this.databaseMessageID) val tempFile = createTempFile() val threadID = storage.getThreadIdForMms(databaseMessageID) - val openGroupV2 = storage.getV2OpenGroup(threadID.toString()) + val openGroupV2 = storage.getV2OpenGroup(threadID) val inputStream = if (openGroupV2 == null) { - DownloadUtilities.downloadFile(tempFile, attachment.url, FileServerAPI.maxFileSize, null) + DownloadUtilities.downloadFile(tempFile, attachment.url) // Assume we're retrieving an attachment for an open group server if the digest is not set if (attachment.digest?.size ?: 0 == 0 || attachment.key.isNullOrEmpty()) { FileInputStream(tempFile) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt index 00432d3ff3..4f7e7ea57c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt @@ -6,13 +6,12 @@ import com.esotericsoftware.kryo.io.Output import nl.komponents.kovenant.Promise import okio.Buffer import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.file_server.FileServerAPI import org.session.libsession.messaging.file_server.FileServerAPIV2 import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.utilities.Data -import org.session.libsession.messaging.utilities.DotNetAPI +import org.session.libsession.utilities.UploadResult import org.session.libsignal.streams.AttachmentCipherOutputStream import org.session.libsignal.messages.SignalServiceAttachmentStream import org.session.libsignal.streams.PaddingInputStream @@ -53,37 +52,28 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider val attachment = messageDataProvider.getScaledSignalAttachmentStream(attachmentID) ?: return handleFailure(Error.NoAttachment) - val v2OpenGroup = storage.getV2OpenGroup(threadID) - val v1OpenGroup = storage.getOpenGroup(threadID) + val v2OpenGroup = storage.getV2OpenGroup(threadID.toLong()) if (v2OpenGroup != null) { val keyAndResult = upload(attachment, v2OpenGroup.server, false) { OpenGroupAPIV2.upload(it, v2OpenGroup.room, v2OpenGroup.server) } handleSuccess(attachment, keyAndResult.first, keyAndResult.second) - } else if (v1OpenGroup == null) { - val keyAndResult = upload(attachment, FileServerAPIV2.SERVER, true) { + } else { + val keyAndResult = upload(attachment, FileServerAPIV2.server, true) { FileServerAPIV2.upload(it) } handleSuccess(attachment, keyAndResult.first, keyAndResult.second) - } else { // V1 open group - val server = v1OpenGroup.server - val pushData = PushAttachmentData(attachment.contentType, attachment.inputStream, - attachment.length, PlaintextOutputStreamFactory(), attachment.listener) - val result = FileServerAPI.shared.uploadAttachment(server, pushData) - handleSuccess(attachment, ByteArray(0), result) } } catch (e: java.lang.Exception) { if (e == Error.NoAttachment) { this.handlePermanentFailure(e) - } else if (e is DotNetAPI.Error && !e.isRetryable) { - this.handlePermanentFailure(e) } else { this.handleFailure(e) } } } - private fun upload(attachment: SignalServiceAttachmentStream, server: String, encrypt: Boolean, upload: (ByteArray) -> Promise): Pair { + private fun upload(attachment: SignalServiceAttachmentStream, server: String, encrypt: Boolean, upload: (ByteArray) -> Promise): Pair { // Key val key = if (encrypt) Util.getSecretBytes(64) else ByteArray(0) // Length @@ -112,10 +102,10 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess val id = upload(data).get() val digest = drb.transmittedDigest // Return - return Pair(key, DotNetAPI.UploadResult(id, "${server}/files/$id", digest)) + return Pair(key, UploadResult(id, "${server}/files/$id", digest)) } - private fun handleSuccess(attachment: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) { + private fun handleSuccess(attachment: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult) { Log.d(TAG, "Attachment uploaded successfully.") delegate?.handleJobSucceeded(this) MessagingModuleConfiguration.shared.messageDataProvider.handleSuccessfulAttachmentUpload(attachmentID, attachment, attachmentKey, uploadResult) diff --git a/libsession/src/main/java/org/session/libsession/messaging/mentions/MentionsManager.kt b/libsession/src/main/java/org/session/libsession/messaging/mentions/MentionsManager.kt index 5ee6452112..d4b1b35c68 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/mentions/MentionsManager.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/mentions/MentionsManager.kt @@ -30,14 +30,8 @@ class MentionsManager(private val userPublicKey: String, private val userDatabas // Prepare val cache = userPublicKeyCache[threadID] ?: return listOf() // Gather candidates - val publicChat = MessagingModuleConfiguration.shared.messageDataProvider.getOpenGroup(threadID) var candidates: List = cache.mapNotNull { publicKey -> - val displayName: String? - if (publicChat != null) { - displayName = userDatabase.getServerDisplayName(publicChat.id, publicKey) - } else { - displayName = userDatabase.getDisplayName(publicKey) - } + val displayName = userDatabase.getDisplayName(publicKey) if (displayName == null) { return@mapNotNull null } if (displayName.startsWith("Anonymous")) { return@mapNotNull null } Mention(publicKey, displayName) diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/Destination.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/Destination.kt index 9a65978afc..ac6d97874c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/Destination.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/Destination.kt @@ -2,7 +2,6 @@ package org.session.libsession.messaging.messages import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.open_groups.OpenGroupV2 -import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupUtil import org.session.libsignal.utilities.toHexString @@ -15,9 +14,6 @@ sealed class Destination { class ClosedGroup(var groupPublicKey: String) : Destination() { internal constructor(): this("") } - class OpenGroup(var channel: Long, var server: String) : Destination() { - internal constructor(): this(0, "") - } class OpenGroupV2(var room: String, var server: String) : Destination() { internal constructor(): this("", "") } @@ -36,10 +32,8 @@ sealed class Destination { } address.isOpenGroup -> { val storage = MessagingModuleConfiguration.shared.storage - val threadID = storage.getThreadID(address.contactIdentifier())!! - when (val openGroup = storage.getV2OpenGroup(threadID) ?: storage.getOpenGroup(threadID)) { - is org.session.libsession.messaging.open_groups.OpenGroup - -> Destination.OpenGroup(openGroup.channel, openGroup.server) + val threadID = storage.getThreadId(address)!! + when (val openGroup = storage.getV2OpenGroup(threadID)) { is org.session.libsession.messaging.open_groups.OpenGroupV2 -> Destination.OpenGroupV2(openGroup.room, openGroup.server) else -> throw Exception("Missing open group for thread with ID: $threadID.") diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt index 6eefd63c7b..cc49cec65b 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/ConfigurationMessage.kt @@ -114,10 +114,9 @@ class ConfigurationMessage(var closedGroups: List, var openGroups: closedGroups.add(closedGroup) } if (group.isOpenGroup) { - val threadID = storage.getThreadID(group.encodedId) ?: continue - val openGroup = storage.getOpenGroup(threadID) + val threadID = storage.getThreadId(group.encodedId) ?: continue val openGroupV2 = storage.getV2OpenGroup(threadID) - val shareUrl = openGroup?.server ?: openGroupV2?.joinURL ?: continue + val shareUrl = openGroupV2?.joinURL ?: continue openGroups.add(shareUrl) } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt deleted file mode 100644 index a39b22ad35..0000000000 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt +++ /dev/null @@ -1,37 +0,0 @@ -package org.session.libsession.messaging.open_groups - -import org.session.libsignal.utilities.JsonUtil - -data class OpenGroup( - val channel: Long, - private val serverURL: String, - val displayName: String, - val isDeletable: Boolean -) { - val server get() = serverURL.toLowerCase() - val id get() = getId(channel, server) - - companion object { - - @JvmStatic fun getId(channel: Long, server: String): String { - return "$server.$channel" - } - - @JvmStatic fun fromJSON(jsonAsString: String): OpenGroup? { - try { - val json = JsonUtil.fromJson(jsonAsString) - val channel = json.get("channel").asLong() - val server = json.get("server").asText().toLowerCase() - val displayName = json.get("displayName").asText() - val isDeletable = json.get("isDeletable").asBoolean() - return OpenGroup(channel, server, displayName, isDeletable) - } catch (e: Exception) { - return null - } - } - } - - fun toJSON(): Map { - return mapOf( "channel" to channel, "server" to server, "displayName" to displayName, "isDeletable" to isDeletable ) - } -} diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPI.kt deleted file mode 100644 index fa3d32c504..0000000000 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPI.kt +++ /dev/null @@ -1,394 +0,0 @@ -package org.session.libsession.messaging.open_groups - -import nl.komponents.kovenant.Promise -import nl.komponents.kovenant.deferred -import nl.komponents.kovenant.functional.map -import nl.komponents.kovenant.then -import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.file_server.FileServerAPI -import org.session.libsession.messaging.utilities.DotNetAPI -import org.session.libsession.utilities.DownloadUtilities -import org.session.libsignal.utilities.retryIfNeeded -import org.session.libsignal.utilities.* -import org.session.libsignal.utilities.Base64 -import org.session.libsignal.utilities.Log -import java.io.ByteArrayOutputStream -import java.text.SimpleDateFormat -import java.util.* - -object OpenGroupAPI: DotNetAPI() { - - private val moderators: HashMap>> = hashMapOf() // Server URL to (channel ID to set of moderator IDs) - - // region Settings - private val fallbackBatchCount = 64 - private val maxRetryCount = 8 - // endregion - - // region Convenience - private val channelInfoType = "net.patter-app.settings" - private val attachmentType = "net.app.core.oembed" - @JvmStatic - val openGroupMessageType = "network.loki.messenger.publicChat" - @JvmStatic - val profilePictureType = "network.loki.messenger.avatar" - - fun getDefaultChats(): List { - return listOf() // Don't auto-join any open groups right now - } - - @JvmStatic - fun isUserModerator(hexEncodedPublicKey: String, channel: Long, server: String): Boolean { - if (moderators[server] != null && moderators[server]!![channel] != null) { - return moderators[server]!![channel]!!.contains(hexEncodedPublicKey) - } - return false - } - // endregion - - // region Public API - fun getMessages(channel: Long, server: String): Promise, Exception> { - Log.d("Loki", "Getting messages for open group with ID: $channel on server: $server.") - val storage = MessagingModuleConfiguration.shared.storage - val parameters = mutableMapOf( "include_annotations" to 1 ) - val lastMessageServerID = storage.getLastMessageServerID(channel, server) - if (lastMessageServerID != null) { - parameters["since_id"] = lastMessageServerID - } else { - parameters["count"] = fallbackBatchCount - parameters["include_deleted"] = 0 - } - return execute(HTTPVerb.GET, server, "channels/$channel/messages", parameters = parameters).then { json -> - try { - val data = json["data"] as List> - val messages = data.mapNotNull { message -> - try { - val isDeleted = message["is_deleted"] as? Boolean ?: false - if (isDeleted) { return@mapNotNull null } - // Ignore messages without annotations - if (message["annotations"] == null) { return@mapNotNull null } - val annotation = (message["annotations"] as List>).find { - ((it["type"] as? String ?: "") == openGroupMessageType) && it["value"] != null - } ?: return@mapNotNull null - val value = annotation["value"] as Map<*, *> - val serverID = message["id"] as? Long ?: (message["id"] as? Int)?.toLong() ?: (message["id"] as String).toLong() - val user = message["user"] as Map<*, *> - val publicKey = user["username"] as String - val displayName = user["name"] as? String ?: "Anonymous" - var profilePicture: OpenGroupMessage.ProfilePicture? = null - if (user["annotations"] != null) { - val profilePictureAnnotation = (user["annotations"] as List>).find { - ((it["type"] as? String ?: "") == profilePictureType) && it["value"] != null - } - val profilePictureAnnotationValue = profilePictureAnnotation?.get("value") as? Map<*, *> - if (profilePictureAnnotationValue != null && profilePictureAnnotationValue["profileKey"] != null && profilePictureAnnotationValue["url"] != null) { - try { - val profileKey = Base64.decode(profilePictureAnnotationValue["profileKey"] as String) - val url = profilePictureAnnotationValue["url"] as String - profilePicture = OpenGroupMessage.ProfilePicture(profileKey, url) - } catch (e: Exception) {} - } - } - @Suppress("NAME_SHADOWING") val body = message["text"] as String - val timestamp = value["timestamp"] as? Long ?: (value["timestamp"] as? Int)?.toLong() ?: (value["timestamp"] as String).toLong() - var quote: OpenGroupMessage.Quote? = null - if (value["quote"] != null) { - val replyTo = message["reply_to"] as? Long ?: (message["reply_to"] as? Int)?.toLong() ?: (message["reply_to"] as String).toLong() - val quoteAnnotation = value["quote"] as? Map<*, *> - val quoteTimestamp = quoteAnnotation?.get("id") as? Long ?: (quoteAnnotation?.get("id") as? Int)?.toLong() ?: (quoteAnnotation?.get("id") as? String)?.toLong() ?: 0L - val author = quoteAnnotation?.get("author") as? String - val text = quoteAnnotation?.get("text") as? String - quote = if (quoteTimestamp > 0L && author != null && text != null) OpenGroupMessage.Quote(quoteTimestamp, author, text, replyTo) else null - } - val attachmentsAsJSON = (message["annotations"] as List>).filter { - ((it["type"] as? String ?: "") == attachmentType) && it["value"] != null - } - val attachments = attachmentsAsJSON.mapNotNull { it["value"] as? Map<*, *> }.mapNotNull { attachmentAsJSON -> - try { - val kindAsString = attachmentAsJSON["lokiType"] as String - val kind = OpenGroupMessage.Attachment.Kind.values().first { it.rawValue == kindAsString } - val id = attachmentAsJSON["id"] as? Long ?: (attachmentAsJSON["id"] as? Int)?.toLong() ?: (attachmentAsJSON["id"] as String).toLong() - val contentType = attachmentAsJSON["contentType"] as String - val size = attachmentAsJSON["size"] as? Int ?: (attachmentAsJSON["size"] as? Long)?.toInt() ?: (attachmentAsJSON["size"] as String).toInt() - val fileName = attachmentAsJSON["fileName"] as? String - val flags = 0 - val url = attachmentAsJSON["url"] as String - val caption = attachmentAsJSON["caption"] as? String - val linkPreviewURL = attachmentAsJSON["linkPreviewUrl"] as? String - val linkPreviewTitle = attachmentAsJSON["linkPreviewTitle"] as? String - if (kind == OpenGroupMessage.Attachment.Kind.LinkPreview && (linkPreviewURL == null || linkPreviewTitle == null)) { - null - } else { - OpenGroupMessage.Attachment(kind, server, id, contentType, size, fileName, flags, 0, 0, caption, url, linkPreviewURL, linkPreviewTitle) - } - } catch (e: Exception) { - Log.d("Loki","Couldn't parse attachment due to error: $e.") - null - } - } - // Set the last message server ID here to avoid the situation where a message doesn't have a valid signature and this function is called over and over - @Suppress("NAME_SHADOWING") val lastMessageServerID = storage.getLastMessageServerID(channel, server) - if (serverID > lastMessageServerID ?: 0) { storage.setLastMessageServerID(channel, server, serverID) } - val hexEncodedSignature = value["sig"] as String - val signatureVersion = value["sigver"] as? Long ?: (value["sigver"] as? Int)?.toLong() ?: (value["sigver"] as String).toLong() - val signature = OpenGroupMessage.Signature(Hex.fromStringCondensed(hexEncodedSignature), signatureVersion) - val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US) - format.timeZone = TimeZone.getTimeZone("GMT") - val dateAsString = message["created_at"] as String - val serverTimestamp = format.parse(dateAsString).time - // Verify the message - val groupMessage = OpenGroupMessage(serverID, publicKey, displayName, body, timestamp, openGroupMessageType, quote, attachments.toMutableList(), profilePicture, signature, serverTimestamp) - if (groupMessage.hasValidSignature()) groupMessage else null - } catch (exception: Exception) { - Log.d("Loki", "Couldn't parse message for open group with ID: $channel on server: $server from: ${JsonUtil.toJson(message)}. Exception: ${exception.message}") - return@mapNotNull null - } - }.sortedBy { it.serverTimestamp } - messages - } catch (exception: Exception) { - Log.d("Loki", "Couldn't parse messages for open group with ID: $channel on server: $server.") - throw exception - } - } - } - - @JvmStatic - fun getDeletedMessageServerIDs(channel: Long, server: String): Promise, Exception> { - Log.d("Loki", "Getting deleted messages for open group with ID: $channel on server: $server.") - val storage = MessagingModuleConfiguration.shared.storage - val parameters = mutableMapOf() - val lastDeletionServerID = storage.getLastDeletionServerID(channel, server) - if (lastDeletionServerID != null) { - parameters["since_id"] = lastDeletionServerID - } else { - parameters["count"] = fallbackBatchCount - } - return execute(HTTPVerb.GET, server, "loki/v1/channel/$channel/deletes", parameters = parameters).then { json -> - try { - val deletedMessageServerIDs = (json["data"] as List>).mapNotNull { deletion -> - try { - val serverID = deletion["id"] as? Long ?: (deletion["id"] as? Int)?.toLong() ?: (deletion["id"] as String).toLong() - val messageServerID = deletion["message_id"] as? Long ?: (deletion["message_id"] as? Int)?.toLong() ?: (deletion["message_id"] as String).toLong() - @Suppress("NAME_SHADOWING") val lastDeletionServerID = storage.getLastDeletionServerID(channel, server) - if (serverID > (lastDeletionServerID ?: 0)) { storage.setLastDeletionServerID(channel, server, serverID) } - messageServerID - } catch (exception: Exception) { - Log.d("Loki", "Couldn't parse deleted message for open group with ID: $channel on server: $server. Exception: ${exception.message}") - return@mapNotNull null - } - } - deletedMessageServerIDs - } catch (exception: Exception) { - Log.d("Loki", "Couldn't parse deleted messages for open group with ID: $channel on server: $server.") - throw exception - } - } - } - - @JvmStatic - fun sendMessage(message: OpenGroupMessage, channel: Long, server: String): Promise { - val deferred = deferred() - val storage = MessagingModuleConfiguration.shared.storage - val userKeyPair = storage.getUserKeyPair() ?: throw Error.Generic - val userDisplayName = storage.getUserDisplayName() ?: throw Error.Generic - ThreadUtils.queue { - val signedMessage = message.sign(userKeyPair.second) - if (signedMessage == null) { - deferred.reject(Error.SigningFailed) - } else { - retryIfNeeded(maxRetryCount) { - Log.d("Loki", "Sending message to open group with ID: $channel on server: $server.") - val parameters = signedMessage.toJSON() - execute(HTTPVerb.POST, server, "channels/$channel/messages", parameters = parameters).then { json -> - try { - val data = json["data"] as Map<*, *> - val serverID = (data["id"] as? Long) ?: (data["id"] as? Int)?.toLong() ?: (data["id"] as String).toLong() - val text = data["text"] as String - val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US) - format.timeZone = TimeZone.getTimeZone("GMT") - val dateAsString = data["created_at"] as String - val timestamp = format.parse(dateAsString).time - OpenGroupMessage(serverID, userKeyPair.first, userDisplayName, text, timestamp, openGroupMessageType, message.quote, message.attachments, null, signedMessage.signature, timestamp) - } catch (exception: Exception) { - Log.d("Loki", "Couldn't parse message for open group with ID: $channel on server: $server.") - throw exception - } - } - }.success { - deferred.resolve(it) - }.fail { - deferred.reject(it) - } - } - } - return deferred.promise - } - - fun deleteMessage(messageServerID: Long, channel: Long, server: String, isSentByUser: Boolean): Promise { - return retryIfNeeded(maxRetryCount) { - val isModerationRequest = !isSentByUser - Log.d("Loki", "Deleting message with ID: $messageServerID from open group with ID: $channel on server: $server (isModerationRequest = $isModerationRequest).") - val endpoint = if (isSentByUser) "channels/$channel/messages/$messageServerID" else "loki/v1/moderation/message/$messageServerID" - execute(HTTPVerb.DELETE, server, endpoint, isJSONRequired = false).then { - Log.d("Loki", "Deleted message with ID: $messageServerID from open group with ID: $channel on server: $server.") - messageServerID - } - } - } - - @JvmStatic - fun deleteMessages(messageServerIDs: List, channel: Long, server: String, isSentByUser: Boolean): Promise, Exception> { - return retryIfNeeded(maxRetryCount) { - val isModerationRequest = !isSentByUser - val parameters = mapOf( "ids" to messageServerIDs.joinToString(",") ) - Log.d("Loki", "Deleting messages with IDs: ${messageServerIDs.joinToString()} from open group with ID: $channel on server: $server (isModerationRequest = $isModerationRequest).") - val endpoint = if (isSentByUser) "loki/v1/messages" else "loki/v1/moderation/messages" - execute(HTTPVerb.DELETE, server, endpoint, parameters = parameters, isJSONRequired = false).then { json -> - Log.d("Loki", "Deleted messages with IDs: $messageServerIDs from open group with ID: $channel on server: $server.") - messageServerIDs - } - } - } - - @JvmStatic - fun getModerators(channel: Long, server: String): Promise, Exception> { - return execute(HTTPVerb.GET, server, "loki/v1/channel/$channel/get_moderators").then { json -> - try { - @Suppress("UNCHECKED_CAST") val moderators = json["moderators"] as? List - val moderatorsAsSet = moderators.orEmpty().toSet() - if (this.moderators[server] != null) { - this.moderators[server]!![channel] = moderatorsAsSet - } else { - this.moderators[server] = hashMapOf( channel to moderatorsAsSet ) - } - moderatorsAsSet - } catch (exception: Exception) { - Log.d("Loki", "Couldn't parse moderators for open group with ID: $channel on server: $server.") - throw exception - } - } - } - - @JvmStatic - fun getChannelInfo(channel: Long, server: String): Promise { - return retryIfNeeded(maxRetryCount) { - val parameters = mapOf( "include_annotations" to 1 ) - execute(HTTPVerb.GET, server, "/channels/$channel", parameters = parameters).then { json -> - try { - val data = json["data"] as Map<*, *> - val annotations = data["annotations"] as List> - val annotation = annotations.find { (it["type"] as? String ?: "") == channelInfoType } ?: throw Error.ParsingFailed - val info = annotation["value"] as Map<*, *> - val displayName = info["name"] as String - val countInfo = data["counts"] as Map<*, *> - val memberCount = countInfo["subscribers"] as? Int ?: (countInfo["subscribers"] as? Long)?.toInt() ?: (countInfo["subscribers"] as String).toInt() - val profilePictureURL = info["avatar"] as String - val publicChatInfo = OpenGroupInfo(displayName, profilePictureURL, memberCount) - MessagingModuleConfiguration.shared.storage.setUserCount(channel, server, memberCount) - publicChatInfo - } catch (exception: Exception) { - Log.d("Loki", "Couldn't parse info for open group with ID: $channel on server: $server.") - throw exception - } - } - } - } - - @JvmStatic - fun updateProfileIfNeeded(channel: Long, server: String, groupID: String, info: OpenGroupInfo, isForcedUpdate: Boolean) { - val storage = MessagingModuleConfiguration.shared.storage - storage.setUserCount(channel, server, info.memberCount) - storage.updateTitle(groupID, info.displayName) - // Download and update profile picture if needed - val oldProfilePictureURL = storage.getOpenGroupProfilePictureURL(channel, server) - if (isForcedUpdate || oldProfilePictureURL != info.profilePictureURL) { - val profilePictureAsByteArray = downloadOpenGroupProfilePicture(server, info.profilePictureURL) ?: return - storage.updateProfilePicture(groupID, profilePictureAsByteArray) - storage.setOpenGroupProfilePictureURL(channel, server, info.profilePictureURL) - } - } - - @JvmStatic - fun downloadOpenGroupProfilePicture(server: String, endpoint: String): ByteArray? { - val url = "${server.removeSuffix("/")}/${endpoint.removePrefix("/")}" - Log.d("Loki", "Downloading open group profile picture from \"$url\".") - val outputStream = ByteArrayOutputStream() - try { - DownloadUtilities.downloadFile(outputStream, url, FileServerAPI.maxFileSize, null) - Log.d("Loki", "Open group profile picture was successfully loaded from \"$url\"") - return outputStream.toByteArray() - } catch (e: Exception) { - Log.d("Loki", "Failed to download open group profile picture from \"$url\" due to error: $e.") - return null - } finally { - outputStream.close() - } - } - - @JvmStatic - fun join(channel: Long, server: String): Promise { - return retryIfNeeded(maxRetryCount) { - execute(HTTPVerb.POST, server, "/channels/$channel/subscribe").then { - Log.d("Loki", "Joined channel with ID: $channel on server: $server.") - } - } - } - - @JvmStatic - fun leave(channel: Long, server: String): Promise { - return retryIfNeeded(maxRetryCount) { - execute(HTTPVerb.DELETE, server, "/channels/$channel/subscribe").then { - Log.d("Loki", "Left channel with ID: $channel on server: $server.") - } - } - } - - @JvmStatic - fun ban(publicKey: String, server: String): Promise { - return retryIfNeeded(maxRetryCount) { - execute(HTTPVerb.POST, server, "/loki/v1/moderation/blacklist/@$publicKey").then { - Log.d("Loki", "Banned user with ID: $publicKey from $server") - } - } - } - - @JvmStatic - fun getDisplayNames(publicKeys: Set, server: String): Promise, Exception> { - return getUserProfiles(publicKeys, server, false).map { json -> - val mapping = mutableMapOf() - for (user in json) { - if (user["username"] != null) { - val publicKey = user["username"] as String - val displayName = user["name"] as? String ?: "Anonymous" - mapping[publicKey] = displayName - } - } - mapping - } - } - - @JvmStatic - fun setDisplayName(newDisplayName: String?, server: String): Promise { - Log.d("Loki", "Updating display name on server: $server.") - val parameters = mapOf( "name" to (newDisplayName ?: "") ) - return execute(HTTPVerb.PATCH, server, "users/me", parameters = parameters).map { Unit } - } - - @JvmStatic - fun setProfilePicture(server: String, profileKey: ByteArray, url: String?): Promise { - return setProfilePicture(server, Base64.encodeBytes(profileKey), url) - } - - fun setProfilePicture(server: String, profileKey: String, url: String?): Promise { - Log.d("Loki", "Updating profile picture on server: $server.") - val value = when (url) { - null -> null - else -> mapOf( "profileKey" to profileKey, "url" to url ) - } - // TODO: This may actually completely replace the annotations, have to double check it - return setSelfAnnotation(server, profilePictureType, value).map { Unit }.fail { - Log.d("Loki", "Failed to update profile picture due to error: $it.") - } - } - // endregion -} diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt index a18e25850d..12e5fc1364 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt @@ -31,8 +31,8 @@ object OpenGroupAPIV2 { private val curve = Curve25519.getInstance(Curve25519.BEST) val defaultRooms = MutableSharedFlow>(replay = 1) - private const val DEFAULT_SERVER_PUBLIC_KEY = "a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238" - const val DEFAULT_SERVER = "http://116.203.70.33" + private const val defaultServerPublicKey = "a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238" + const val defaultServer = "http://116.203.70.33" sealed class Error(message: String) : Exception(message) { object Generic : Error("An error occurred.") @@ -45,7 +45,7 @@ object OpenGroupAPIV2 { data class DefaultGroup(val id: String, val name: String, val image: ByteArray?) { - val joinURL: String get() = "$DEFAULT_SERVER/$id?public_key=$DEFAULT_SERVER_PUBLIC_KEY" + val joinURL: String get() = "$defaultServer/$id?public_key=$defaultServerPublicKey" } data class Info(val id: String, val name: String, val imageID: String?) @@ -60,7 +60,7 @@ object OpenGroupAPIV2 { ) { companion object { - val EMPTY = MessageDeletion() + val empty = MessageDeletion() } } @@ -125,9 +125,7 @@ object OpenGroupAPIV2 { if (e is OnionRequestAPI.HTTPRequestFailedAtDestinationException && e.statusCode == 401) { val storage = MessagingModuleConfiguration.shared.storage if (request.room != null) { - storage.removeAuthToken("${request.server}.${request.room}") - } else { - storage.removeAuthToken(request.server) + storage.removeAuthToken(request.room, request.server) } } } @@ -237,7 +235,7 @@ object OpenGroupAPIV2 { fun getMessages(room: String, server: String): Promise, Exception> { val storage = MessagingModuleConfiguration.shared.storage val queryParameters = mutableMapOf() - storage.getLastMessageServerId(room, server)?.let { lastId -> + storage.getLastMessageServerID(room, server)?.let { lastId -> queryParameters += "from_server_id" to lastId.toString() } val request = Request(verb = GET, room = room, server = server, endpoint = "messages", queryParameters = queryParameters) @@ -250,7 +248,7 @@ object OpenGroupAPIV2 { private fun parseMessages(room: String, server: String, rawMessages: List>): List { val storage = MessagingModuleConfiguration.shared.storage - val lastMessageServerID = storage.getLastMessageServerId(room, server) ?: 0 + val lastMessageServerID = storage.getLastMessageServerID(room, server) ?: 0 var currentLastMessageServerID = lastMessageServerID val messages = rawMessages.mapNotNull { json -> json as Map @@ -274,7 +272,7 @@ object OpenGroupAPIV2 { null } } - storage.setLastMessageServerId(room, server, currentLastMessageServerID) + storage.setLastMessageServerID(room, server, currentLastMessageServerID) return messages } // endregion @@ -291,7 +289,7 @@ object OpenGroupAPIV2 { fun getDeletedMessages(room: String, server: String): Promise, Exception> { val storage = MessagingModuleConfiguration.shared.storage val queryParameters = mutableMapOf() - storage.getLastDeletionServerId(room, server)?.let { last -> + storage.getLastDeletionServerID(room, server)?.let { last -> queryParameters["from_server_id"] = last.toString() } val request = Request(verb = GET, room = room, server = server, endpoint = "deleted_messages", queryParameters = queryParameters) @@ -299,10 +297,10 @@ object OpenGroupAPIV2 { val type = TypeFactory.defaultInstance().constructCollectionType(List::class.java, MessageDeletion::class.java) val idsAsString = JsonUtil.toJson(json["ids"]) val serverIDs = JsonUtil.fromJson>(idsAsString, type) ?: throw Error.ParsingFailed - val lastMessageServerId = storage.getLastDeletionServerId(room, server) ?: 0 - val serverID = serverIDs.maxByOrNull {it.id } ?: MessageDeletion.EMPTY + val lastMessageServerId = storage.getLastDeletionServerID(room, server) ?: 0 + val serverID = serverIDs.maxByOrNull {it.id } ?: MessageDeletion.empty if (serverID.id > lastMessageServerId) { - storage.setLastDeletionServerId(room, server, serverID.id) + storage.setLastDeletionServerID(room, server, serverID.id) } serverIDs } @@ -361,8 +359,8 @@ object OpenGroupAPIV2 { CompactPollRequest( roomID = room, authToken = authToken, - fromDeletionServerID = storage.getLastDeletionServerId(room, server), - fromMessageServerID = storage.getLastMessageServerId(room, server) + fromDeletionServerID = storage.getLastDeletionServerID(room, server), + fromMessageServerID = storage.getLastMessageServerID(room, server) ) } val request = Request(verb = POST, room = null, server = server, endpoint = "compact_poll", isAuthRequired = false, parameters = mapOf( "requests" to requests )) @@ -386,10 +384,10 @@ object OpenGroupAPIV2 { val type = TypeFactory.defaultInstance().constructCollectionType(List::class.java, MessageDeletion::class.java) val idsAsString = JsonUtil.toJson(json["deletions"]) val deletedServerIDs = JsonUtil.fromJson>(idsAsString, type) ?: throw Error.ParsingFailed - val lastDeletionServerID = storage.getLastDeletionServerId(roomID, server) ?: 0 - val serverID = deletedServerIDs.maxByOrNull { it.id } ?: MessageDeletion.EMPTY + val lastDeletionServerID = storage.getLastDeletionServerID(roomID, server) ?: 0 + val serverID = deletedServerIDs.maxByOrNull { it.id } ?: MessageDeletion.empty if (serverID.id > lastDeletionServerID) { - storage.setLastDeletionServerId(roomID, server, serverID.id) + storage.setLastDeletionServerID(roomID, server, serverID.id) } // Messages val rawMessages = json["messages"] as? List> ?: return@mapNotNull null @@ -405,8 +403,8 @@ object OpenGroupAPIV2 { fun getDefaultRoomsIfNeeded(): Promise, Exception> { val storage = MessagingModuleConfiguration.shared.storage - storage.setOpenGroupPublicKey(DEFAULT_SERVER, DEFAULT_SERVER_PUBLIC_KEY) - return getAllRooms(DEFAULT_SERVER).map { groups -> + storage.setOpenGroupPublicKey(defaultServer, defaultServerPublicKey) + return getAllRooms(defaultServer).map { groups -> val earlyGroups = groups.map { group -> DefaultGroup(group.id, group.name, null) } @@ -417,7 +415,7 @@ object OpenGroupAPIV2 { } } val images = groups.map { group -> - group.id to downloadOpenGroupProfilePicture(group.id, DEFAULT_SERVER) + group.id to downloadOpenGroupProfilePicture(group.id, defaultServer) }.toMap() groups.map { group -> val image = try { diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupMessage.kt deleted file mode 100644 index bc4f465170..0000000000 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupMessage.kt +++ /dev/null @@ -1,247 +0,0 @@ -package org.session.libsession.messaging.open_groups - -import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.messages.visible.VisibleMessage -import org.session.libsignal.utilities.removing05PrefixIfNeeded -import org.session.libsignal.utilities.Hex -import org.session.libsignal.utilities.Log -import org.whispersystems.curve25519.Curve25519 - -data class OpenGroupMessage( - val serverID: Long?, - val senderPublicKey: String, - val displayName: String, - val body: String, - val timestamp: Long, - val type: String, - val quote: Quote?, - val attachments: MutableList, - val profilePicture: ProfilePicture?, - val signature: Signature?, - val serverTimestamp: Long, -) { - - // region Settings - companion object { - fun from(message: VisibleMessage, server: String): OpenGroupMessage? { - val storage = MessagingModuleConfiguration.shared.storage - val userPublicKey = storage.getUserPublicKey() ?: return null - val attachmentIDs = message.attachmentIDs - // Validation - if (!message.isValid()) { return null } // Should be valid at this point - // Quote - val quote: Quote? = { - val quote = message.quote - if (quote != null && quote.isValid()) { - val quotedMessageBody = quote.text ?: quote.timestamp!!.toString() - val serverID = storage.getQuoteServerID(quote.timestamp!!, quote.publicKey!!) - Quote(quote.timestamp!!, quote.publicKey!!, quotedMessageBody, serverID) - } else { - null - } - }() - // Message - val displayname = storage.getUserDisplayName() ?: "Anonymous" - val text = message.text - val body = if (text.isNullOrEmpty()) message.sentTimestamp.toString() else text // The back-end doesn't accept messages without a body so we use this as a workaround - val result = OpenGroupMessage(null, userPublicKey, displayname, body, message.sentTimestamp!!, OpenGroupAPI.openGroupMessageType, quote, mutableListOf(), null, null, 0) - // Link preview - val linkPreview = message.linkPreview - linkPreview?.let { - if (!linkPreview.isValid()) { return@let } - val attachmentID = linkPreview.attachmentID ?: return@let - val attachment = MessagingModuleConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(attachmentID) ?: return@let - val openGroupLinkPreview = Attachment( - Attachment.Kind.LinkPreview, - server, - attachment.id, - attachment.contentType!!, - attachment.size.get(), - attachment.fileName.orNull(), - 0, - attachment.width, - attachment.height, - attachment.caption.orNull(), - attachment.url, - linkPreview.url, - linkPreview.title) - result.attachments.add(openGroupLinkPreview) - } - // Attachments - val attachments = message.attachmentIDs.mapNotNull { - val attachment = MessagingModuleConfiguration.shared.messageDataProvider.getSignalAttachmentPointer(it) ?: return@mapNotNull null - return@mapNotNull Attachment( - Attachment.Kind.Attachment, - server, - attachment.id, - attachment.contentType!!, - attachment.size.orNull(), - attachment.fileName.orNull() ?: "", - 0, - attachment.width, - attachment.height, - attachment.caption.orNull(), - attachment.url, - null, - null) - } - result.attachments.addAll(attachments) - // Return - return result - } - - private val curve = Curve25519.getInstance(Curve25519.BEST) - private val signatureVersion: Long = 1 - private val attachmentType = "net.app.core.oembed" - } - // endregion - - // region Types - data class ProfilePicture( - val profileKey: ByteArray, - val url: String, - ) - - data class Quote( - val quotedMessageTimestamp: Long, - val quoteePublicKey: String, - val quotedMessageBody: String, - val quotedMessageServerID: Long? = null, - ) - - data class Signature( - val data: ByteArray, - val version: Long, - ) - - data class Attachment( - val kind: Kind, - val server: String, - val serverID: Long, - val contentType: String, - val size: Int, - val fileName: String?, - val flags: Int, - val width: Int, - val height: Int, - val caption: String?, - val url: String, - /** - Guaranteed to be non-`nil` if `kind` is `LinkPreview`. - */ - val linkPreviewURL: String?, - /** - Guaranteed to be non-`nil` if `kind` is `LinkPreview`. - */ - val linkPreviewTitle: String?, - ) { - val dotNetAPIType = when { - contentType.startsWith("image") -> "photo" - contentType.startsWith("video") -> "video" - contentType.startsWith("audio") -> "audio" - else -> "other" - } - - enum class Kind(val rawValue: String) { - Attachment("attachment"), LinkPreview("preview") - } - } - // endregion - - // region Initialization - constructor(hexEncodedPublicKey: String, displayName: String, body: String, timestamp: Long, type: String, quote: Quote?, attachments: List) - : this(null, hexEncodedPublicKey, displayName, body, timestamp, type, quote, attachments as MutableList, null, null, 0) - // endregion - - // region Crypto - internal fun sign(privateKey: ByteArray): OpenGroupMessage? { - val data = getValidationData(signatureVersion) - if (data == null) { - Log.d("Loki", "Failed to sign public chat message.") - return null - } - try { - val signatureData = curve.calculateSignature(privateKey, data) - val signature = Signature(signatureData, signatureVersion) - return copy(signature = signature) - } catch (e: Exception) { - Log.d("Loki", "Failed to sign public chat message due to error: ${e.message}.") - return null - } - } - - internal fun hasValidSignature(): Boolean { - if (signature == null) { return false } - val data = getValidationData(signature.version) ?: return false - val publicKey = Hex.fromStringCondensed(senderPublicKey.removing05PrefixIfNeeded()) - try { - return curve.verifySignature(publicKey, data, signature.data) - } catch (e: Exception) { - Log.d("Loki", "Failed to verify public chat message due to error: ${e.message}.") - return false - } - } - // endregion - - // region Parsing - internal fun toJSON(): Map { - val value = mutableMapOf("timestamp" to timestamp) - if (quote != null) { - value["quote"] = mapOf("id" to quote.quotedMessageTimestamp, "author" to quote.quoteePublicKey, "text" to quote.quotedMessageBody) - } - if (signature != null) { - value["sig"] = Hex.toStringCondensed(signature.data) - value["sigver"] = signature.version - } - val annotation = mapOf("type" to type, "value" to value) - val annotations = mutableListOf(annotation) - attachments.forEach { attachment -> - val attachmentValue = mutableMapOf( - // Fields required by the .NET API - "version" to 1, - "type" to attachment.dotNetAPIType, - // Custom fields - "lokiType" to attachment.kind.rawValue, - "server" to attachment.server, - "id" to attachment.serverID, - "contentType" to attachment.contentType, - "size" to attachment.size, - "fileName" to attachment.fileName, - "flags" to attachment.flags, - "width" to attachment.width, - "height" to attachment.height, - "url" to attachment.url - ) - if (attachment.caption != null) { attachmentValue["caption"] = attachment.caption } - if (attachment.linkPreviewURL != null) { attachmentValue["linkPreviewUrl"] = attachment.linkPreviewURL } - if (attachment.linkPreviewTitle != null) { attachmentValue["linkPreviewTitle"] = attachment.linkPreviewTitle } - val attachmentAnnotation = mapOf("type" to attachmentType, "value" to attachmentValue) - annotations.add(attachmentAnnotation) - } - val result = mutableMapOf("text" to body, "annotations" to annotations) - if (quote?.quotedMessageServerID != null) { - result["reply_to"] = quote.quotedMessageServerID - } - return result - } - // endregion - - // region Convenience - private fun getValidationData(signatureVersion: Long): ByteArray? { - var string = "${body.trim()}$timestamp" - if (quote != null) { - string += "${quote.quotedMessageTimestamp}${quote.quoteePublicKey}${quote.quotedMessageBody.trim()}" - if (quote.quotedMessageServerID != null) { - string += "${quote.quotedMessageServerID}" - } - } - string += attachments.sortedBy { it.serverID }.map { it.serverID }.joinToString("") - string += "$signatureVersion" - try { - return string.toByteArray(Charsets.UTF_8) - } catch (exception: Exception) { - return null - } - } - // endregion -} diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index d08bcfcfad..23b0df99c0 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -54,10 +54,11 @@ object MessageSender { // Convenience fun send(message: Message, destination: Destination): Promise { - if (destination is Destination.OpenGroup || destination is Destination.OpenGroupV2) { + if (destination is Destination.OpenGroupV2) { return sendToOpenGroupDestination(destination, message) + } else { + return sendToSnodeDestination(destination, message) } - return sendToSnodeDestination(destination, message) } // One-on-One Chats & Closed Groups @@ -84,7 +85,7 @@ object MessageSender { when (destination) { is Destination.Contact -> message.recipient = destination.publicKey is Destination.ClosedGroup -> message.recipient = destination.groupPublicKey - is Destination.OpenGroup, is Destination.OpenGroupV2 -> throw IllegalStateException("Destination should not be an open group.") + is Destination.OpenGroupV2 -> throw IllegalStateException("Destination should not be an open group.") } // Validate the message if (!message.isValid()) { throw Error.InvalidMessage } @@ -122,7 +123,7 @@ object MessageSender { val encryptionKeyPair = MessagingModuleConfiguration.shared.storage.getLatestClosedGroupEncryptionKeyPair(destination.groupPublicKey)!! ciphertext = MessageEncrypter.encrypt(plaintext, encryptionKeyPair.hexEncodedPublicKey) } - is Destination.OpenGroup, is Destination.OpenGroupV2 -> throw IllegalStateException("Destination should not be open group.") + is Destination.OpenGroupV2 -> throw IllegalStateException("Destination should not be open group.") } // Wrap the result val kind: SignalServiceProtos.Envelope.Type @@ -136,7 +137,7 @@ object MessageSender { kind = SignalServiceProtos.Envelope.Type.CLOSED_GROUP_MESSAGE senderPublicKey = destination.groupPublicKey } - is Destination.OpenGroup, is Destination.OpenGroupV2 -> throw IllegalStateException("Destination should not be open group.") + is Destination.OpenGroupV2 -> throw IllegalStateException("Destination should not be open group.") } val wrappedMessage = MessageWrapper.wrap(kind, message.sentTimestamp!!, senderPublicKey, ciphertext) // Send the result @@ -203,27 +204,6 @@ object MessageSender { try { when (destination) { is Destination.Contact, is Destination.ClosedGroup -> throw IllegalStateException("Invalid destination.") - is Destination.OpenGroup -> { - message.recipient = "${destination.server}.${destination.channel}" - val server = destination.server - val channel = destination.channel - // Validate the message - if (message !is VisibleMessage || !message.isValid()) { - throw Error.InvalidMessage - } - // Convert the message to an open group message - val openGroupMessage = OpenGroupMessage.from(message, server) ?: run { - throw Error.InvalidMessage - } - // Send the result - OpenGroupAPI.sendMessage(openGroupMessage, channel, server).success { - message.openGroupServerMessageID = it.serverID - handleSuccessfulMessageSend(message, destination) - deferred.resolve(Unit) - }.fail { - handleFailure(it) - } - } is Destination.OpenGroupV2 -> { message.recipient = "${destination.server}.${destination.room}" val server = destination.server @@ -275,7 +255,7 @@ object MessageSender { // Track the open group server message ID if (message.openGroupServerMessageID != null && destination is Destination.OpenGroupV2) { val encoded = GroupUtil.getEncodedOpenGroupID("${destination.server}.${destination.room}".toByteArray()) - val threadID = storage.getThreadIdFor(Address.fromSerialized(encoded)) + val threadID = storage.getThreadId(Address.fromSerialized(encoded)) if (threadID != null && threadID >= 0) { storage.setOpenGroupServerMessageID(messageID, message.openGroupServerMessageID!!, threadID, !(message as VisibleMessage).isMediaMessage()) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt index 049514efb1..6f8b0a6647 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt @@ -192,8 +192,8 @@ fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List>() - - // region Convenience - private val userHexEncodedPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey() ?: "" - private var displayNameUpdates = setOf() - // endregion - - // region Settings - companion object { - private val pollForNewMessagesInterval: Long = 10 * 1000 - private val pollForDeletedMessagesInterval: Long = 60 * 1000 - private val pollForModeratorsInterval: Long = 10 * 60 * 1000 - private val pollForDisplayNamesInterval: Long = 60 * 1000 - } - // endregion - - // region Lifecycle - fun startIfNeeded() { - if (hasStarted || executorService == null) return - cancellableFutures += listOf( - executorService.scheduleAtFixedRate(::pollForNewMessages,0, pollForNewMessagesInterval, TimeUnit.MILLISECONDS), - executorService.scheduleAtFixedRate(::pollForDeletedMessages,0, pollForDeletedMessagesInterval, TimeUnit.MILLISECONDS), - executorService.scheduleAtFixedRate(::pollForModerators,0, pollForModeratorsInterval, TimeUnit.MILLISECONDS), - executorService.scheduleAtFixedRate(::pollForDisplayNames,0, pollForDisplayNamesInterval, TimeUnit.MILLISECONDS) - ) - hasStarted = true - } - - fun stop() { - cancellableFutures.forEach { future -> - future.cancel(false) - } - cancellableFutures.clear() - hasStarted = false - } - // endregion - - // region Polling - fun pollForNewMessages(): Promise { - return pollForNewMessagesInternal(false) - } - - private fun pollForNewMessagesInternal(isBackgroundPoll: Boolean): Promise { - if (isPollOngoing) { return Promise.of(Unit) } - isPollOngoing = true - val deferred = deferred() - // Kovenant propagates a context to chained promises, so OpenGroupAPI.sharedContext should be used for all of the below - OpenGroupAPI.getMessages(openGroup.channel, openGroup.server).successBackground { messages -> - // Process messages in the background - messages.forEach { message -> - try { - val senderPublicKey = message.senderPublicKey - fun generateDisplayName(rawDisplayName: String): String { - return "$rawDisplayName (...${senderPublicKey.takeLast(8)})" - } - val senderDisplayName = MessagingModuleConfiguration.shared.storage.getOpenGroupDisplayName(senderPublicKey, openGroup.channel, openGroup.server) ?: generateDisplayName(message.displayName) - val id = openGroup.id.toByteArray() - // Main message - val dataMessageProto = DataMessage.newBuilder() - val body = if (message.body == message.timestamp.toString()) { "" } else { message.body } - dataMessageProto.setBody(body) - dataMessageProto.setTimestamp(message.timestamp) - // Attachments - val attachmentProtos = message.attachments.mapNotNull { attachment -> - try { - if (attachment.kind != OpenGroupMessage.Attachment.Kind.Attachment) { return@mapNotNull null } - val attachmentProto = AttachmentPointer.newBuilder() - attachmentProto.setId(attachment.serverID) - attachmentProto.setContentType(attachment.contentType) - attachmentProto.setSize(attachment.size) - attachmentProto.setFileName(attachment.fileName) - attachmentProto.setFlags(attachment.flags) - attachmentProto.setWidth(attachment.width) - attachmentProto.setHeight(attachment.height) - attachment.caption?.let { attachmentProto.setCaption(it) } - attachmentProto.setUrl(attachment.url) - attachmentProto.build() - } catch (e: Exception) { - Log.e("Loki","Failed to parse attachment as proto",e) - null - } - } - dataMessageProto.addAllAttachments(attachmentProtos) - // Link preview - val linkPreview = message.attachments.firstOrNull { it.kind == OpenGroupMessage.Attachment.Kind.LinkPreview } - if (linkPreview != null) { - val linkPreviewProto = DataMessage.Preview.newBuilder() - linkPreviewProto.setUrl(linkPreview.linkPreviewURL!!) - linkPreviewProto.setTitle(linkPreview.linkPreviewTitle!!) - val attachmentProto = AttachmentPointer.newBuilder() - attachmentProto.setId(linkPreview.serverID) - attachmentProto.setContentType(linkPreview.contentType) - attachmentProto.setSize(linkPreview.size) - attachmentProto.setFileName(linkPreview.fileName) - attachmentProto.setFlags(linkPreview.flags) - attachmentProto.setWidth(linkPreview.width) - attachmentProto.setHeight(linkPreview.height) - linkPreview.caption?.let { attachmentProto.setCaption(it) } - attachmentProto.setUrl(linkPreview.url) - linkPreviewProto.setImage(attachmentProto.build()) - dataMessageProto.addPreview(linkPreviewProto.build()) - } - // Quote - val quote = message.quote - if (quote != null) { - val quoteProto = DataMessage.Quote.newBuilder() - quoteProto.setId(quote.quotedMessageTimestamp) - quoteProto.setAuthor(quote.quoteePublicKey) - if (quote.quotedMessageBody != quote.quotedMessageTimestamp.toString()) { quoteProto.setText(quote.quotedMessageBody) } - dataMessageProto.setQuote(quoteProto.build()) - } - val messageServerID = message.serverID - // Profile - val profileProto = DataMessage.LokiProfile.newBuilder() - profileProto.setDisplayName(senderDisplayName) - val profilePicture = message.profilePicture - if (profilePicture != null) { - profileProto.setProfilePicture(profilePicture.url) - dataMessageProto.setProfileKey(ByteString.copyFrom(profilePicture.profileKey)) - } - dataMessageProto.setProfile(profileProto.build()) - /* TODO: the signal service proto needs to be synced with iOS - // Open group info - if (messageServerID != null) { - val openGroupProto = PublicChatInfo.newBuilder() - openGroupProto.setServerID(messageServerID) - dataMessageProto.setPublicChatInfo(openGroupProto.build()) - } - */ - // Signal group context - val groupProto = GroupContext.newBuilder() - groupProto.setId(ByteString.copyFrom(id)) - groupProto.setType(GroupContext.Type.DELIVER) - groupProto.setName(openGroup.displayName) - dataMessageProto.setGroup(groupProto.build()) - // Content - val content = Content.newBuilder() - content.setDataMessage(dataMessageProto.build()) - // Envelope - val builder = Envelope.newBuilder() - builder.type = Envelope.Type.SESSION_MESSAGE - builder.source = senderPublicKey - builder.sourceDevice = 1 - builder.setContent(content.build().toByteString()) - builder.timestamp = message.timestamp - builder.serverTimestamp = message.serverTimestamp - val envelope = builder.build() - val job = MessageReceiveJob(envelope.toByteArray(), messageServerID, openGroup.id) - Log.d("Loki", "Scheduling Job $job") - if (isBackgroundPoll) { - job.executeAsync().always { deferred.resolve(Unit) } - // The promise is just used to keep track of when we're done - } else { - JobQueue.shared.add(job) - } - } catch (e: Exception) { - Log.e("Loki", "Exception parsing message", e) - } - } - displayNameUpdates = displayNameUpdates + messages.map { it.senderPublicKey }.toSet() - userHexEncodedPublicKey - executorService?.schedule(::pollForDisplayNames, 0, TimeUnit.MILLISECONDS) - isCaughtUp = true - isPollOngoing = false - deferred.resolve(Unit) - }.fail { - Log.d("Loki", "Failed to get messages for group chat with ID: ${openGroup.channel} on server: ${openGroup.server}.") - isPollOngoing = false - } - return deferred.promise - } - - private fun pollForDisplayNames() { - if (displayNameUpdates.isEmpty()) { return } - val hexEncodedPublicKeys = displayNameUpdates - displayNameUpdates = setOf() - OpenGroupAPI.getDisplayNames(hexEncodedPublicKeys, openGroup.server).successBackground { mapping -> - for (pair in mapping.entries) { - if (pair.key == userHexEncodedPublicKey) continue - val senderDisplayName = "${pair.value} (...${pair.key.substring(pair.key.count() - 8)})" - MessagingModuleConfiguration.shared.storage.setOpenGroupDisplayName(pair.key, openGroup.channel, openGroup.server, senderDisplayName) - } - }.fail { - displayNameUpdates = displayNameUpdates.union(hexEncodedPublicKeys) - } - } - - private fun pollForDeletedMessages() { - val messagingModule = MessagingModuleConfiguration.shared - val address = GroupUtil.getEncodedOpenGroupID(openGroup.id.toByteArray()) - val threadId = messagingModule.storage.getThreadIdFor(Address.fromSerialized(address)) ?: return - OpenGroupAPI.getDeletedMessageServerIDs(openGroup.channel, openGroup.server).success { deletedMessageServerIDs -> - val deletedMessageIDs = deletedMessageServerIDs.mapNotNull { messagingModule.messageDataProvider.getMessageID(it, threadId) } - deletedMessageIDs.forEach { (messageId, isSms) -> - MessagingModuleConfiguration.shared.messageDataProvider.deleteMessage(messageId, isSms) - } - }.fail { - Log.d("Loki", "Failed to get deleted messages for group chat with ID: ${openGroup.channel} on server: ${openGroup.server}.") - } - } - - private fun pollForModerators() { - OpenGroupAPI.getModerators(openGroup.channel, openGroup.server) - } - // endregion -} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt index 320c3afd97..a6649c5150 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt @@ -77,7 +77,7 @@ class OpenGroupPollerV2(private val server: String, private val executorService: val storage = MessagingModuleConfiguration.shared.storage val dataProvider = MessagingModuleConfiguration.shared.messageDataProvider val groupID = GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray()) - val threadID = storage.getThreadIdFor(Address.fromSerialized(groupID)) ?: return + val threadID = storage.getThreadId(Address.fromSerialized(groupID)) ?: return val deletedMessageIDs = deletedMessageServerIDs.mapNotNull { serverID -> val messageID = dataProvider.getMessageID(serverID, threadID) if (messageID == null) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/utilities/DotNetAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/utilities/DotNetAPI.kt deleted file mode 100644 index fc9751f998..0000000000 --- a/libsession/src/main/java/org/session/libsession/messaging/utilities/DotNetAPI.kt +++ /dev/null @@ -1,269 +0,0 @@ -package org.session.libsession.messaging.utilities - -import nl.komponents.kovenant.Promise -import nl.komponents.kovenant.functional.bind -import nl.komponents.kovenant.functional.map -import nl.komponents.kovenant.then -import okhttp3.* - -import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.snode.OnionRequestAPI -import org.session.libsession.messaging.file_server.FileServerAPI - -import org.session.libsignal.crypto.DiffieHellman -import org.session.libsignal.streams.ProfileCipherOutputStream -import org.session.libsignal.exceptions.NonSuccessfulResponseCodeException -import org.session.libsignal.exceptions.PushNetworkException -import org.session.libsignal.streams.StreamDetails -import org.session.libsignal.utilities.ProfileAvatarData -import org.session.libsignal.utilities.PushAttachmentData -import org.session.libsignal.streams.DigestingRequestBody -import org.session.libsignal.streams.ProfileCipherOutputStreamFactory -import org.session.libsignal.utilities.Hex -import org.session.libsignal.utilities.JsonUtil -import org.session.libsignal.utilities.HTTP -import org.session.libsignal.utilities.* -import org.session.libsignal.utilities.Base64 -import org.session.libsignal.utilities.Log -import java.util.* - -/** - * Base class that provides utilities for .NET based APIs. - */ -open class DotNetAPI { - - internal enum class HTTPVerb { GET, PUT, POST, DELETE, PATCH } - - // Error - internal sealed class Error(val description: String) : Exception(description) { - object Generic : Error("An error occurred.") - object InvalidURL : Error("Invalid URL.") - object ParsingFailed : Error("Invalid file server response.") - object SigningFailed : Error("Couldn't sign message.") - object EncryptionFailed : Error("Couldn't encrypt file.") - object DecryptionFailed : Error("Couldn't decrypt file.") - object MaxFileSizeExceeded : Error("Maximum file size exceeded.") - object TokenExpired: Error("Token expired.") // Session Android - - internal val isRetryable: Boolean = false - } - - companion object { - private val authTokenRequestCache = hashMapOf>() - } - - public data class UploadResult(val id: Long, val url: String, val digest: ByteArray?) - - fun getAuthToken(server: String): Promise { - val storage = MessagingModuleConfiguration.shared.storage - val token = storage.getAuthToken(server) - if (token != null) { return Promise.of(token) } - // Avoid multiple token requests to the server by caching - var promise = authTokenRequestCache[server] - if (promise == null) { - promise = requestNewAuthToken(server).bind { submitAuthToken(it, server) }.then { newToken -> - storage.setAuthToken(server, newToken) - newToken - }.always { - authTokenRequestCache.remove(server) - } - authTokenRequestCache[server] = promise - } - return promise - } - - private fun requestNewAuthToken(server: String): Promise { - Log.d("Loki", "Requesting auth token for server: $server.") - val userKeyPair = MessagingModuleConfiguration.shared.storage.getUserKeyPair() ?: throw Error.Generic - val parameters: Map = mapOf( "pubKey" to userKeyPair.first ) - return execute(HTTPVerb.GET, server, "loki/v1/get_challenge", false, parameters).map { json -> - try { - val base64EncodedChallenge = json["cipherText64"] as String - val challenge = Base64.decode(base64EncodedChallenge) - val base64EncodedServerPublicKey = json["serverPubKey64"] as String - var serverPublicKey = Base64.decode(base64EncodedServerPublicKey) - // Discard the "05" prefix if needed - if (serverPublicKey.count() == 33) { - val hexEncodedServerPublicKey = Hex.toStringCondensed(serverPublicKey) - serverPublicKey = Hex.fromStringCondensed(hexEncodedServerPublicKey.removing05PrefixIfNeeded()) - } - // The challenge is prefixed by the 16 bit IV - val tokenAsData = DiffieHellman.decrypt(challenge, serverPublicKey, userKeyPair.second) - val token = tokenAsData.toString(Charsets.UTF_8) - token - } catch (exception: Exception) { - Log.d("Loki", "Couldn't parse auth token for server: $server.") - throw exception - } - } - } - - private fun submitAuthToken(token: String, server: String): Promise { - Log.d("Loki", "Submitting auth token for server: $server.") - val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey() ?: throw Error.Generic - val parameters = mapOf( "pubKey" to userPublicKey, "token" to token ) - return execute(HTTPVerb.POST, server, "loki/v1/submit_challenge", false, parameters, isJSONRequired = false).map { token } - } - - internal fun execute(verb: HTTPVerb, server: String, endpoint: String, isAuthRequired: Boolean = true, parameters: Map = mapOf(), isJSONRequired: Boolean = true): Promise, Exception> { - fun execute(token: String?): Promise, Exception> { - val sanitizedEndpoint = endpoint.removePrefix("/") - var url = "$server/$sanitizedEndpoint" - if (verb == HTTPVerb.GET || verb == HTTPVerb.DELETE) { - val queryParameters = parameters.map { "${it.key}=${it.value}" }.joinToString("&") - if (queryParameters.isNotEmpty()) { url += "?$queryParameters" } - } - var request = Request.Builder().url(url) - if (isAuthRequired) { - if (token == null) { throw IllegalStateException() } - request = request.header("Authorization", "Bearer $token") - } - when (verb) { - HTTPVerb.GET -> request = request.get() - HTTPVerb.DELETE -> request = request.delete() - else -> { - val parametersAsJSON = JsonUtil.toJson(parameters) - val body = RequestBody.create(MediaType.get("application/json"), parametersAsJSON) - when (verb) { - HTTPVerb.PUT -> request = request.put(body) - HTTPVerb.POST -> request = request.post(body) - HTTPVerb.PATCH -> request = request.patch(body) - else -> throw IllegalStateException() - } - } - } - val serverPublicKeyPromise = if (server == FileServerAPI.shared.server) Promise.of(FileServerAPI.fileServerPublicKey) - else FileServerAPI.shared.getPublicKeyForOpenGroupServer(server) - return serverPublicKeyPromise.bind { serverPublicKey -> - OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, isJSONRequired = isJSONRequired).recover { exception -> - if (exception is HTTP.HTTPRequestFailedException) { - val statusCode = exception.statusCode - if (statusCode == 401 || statusCode == 403) { - MessagingModuleConfiguration.shared.storage.setAuthToken(server, null) - throw Error.TokenExpired - } - } - throw exception - } - } - } - return if (isAuthRequired) { - getAuthToken(server).bind { execute(it) } - } else { - execute(null) - } - } - - internal fun getUserProfiles(publicKeys: Set, server: String, includeAnnotations: Boolean): Promise>, Exception> { - val parameters = mapOf( "include_user_annotations" to includeAnnotations.toInt(), "ids" to publicKeys.joinToString { "@$it" } ) - return execute(HTTPVerb.GET, server, "users", parameters = parameters).map { json -> - val data = json["data"] as? List> - if (data == null) { - Log.d("Loki", "Couldn't parse user profiles for: $publicKeys from: $json.") - throw Error.ParsingFailed - } - data!! // For some reason the compiler can't infer that this can't be null at this point - } - } - - internal fun setSelfAnnotation(server: String, type: String, newValue: Any?): Promise, Exception> { - val annotation = mutableMapOf( "type" to type ) - if (newValue != null) { annotation["value"] = newValue } - val parameters = mapOf( "annotations" to listOf( annotation ) ) - return execute(HTTPVerb.PATCH, server, "users/me", parameters = parameters) - } - - // UPLOAD - - // TODO: migrate to v2 file server - @Throws(PushNetworkException::class, NonSuccessfulResponseCodeException::class) - fun uploadAttachment(server: String, attachment: PushAttachmentData): UploadResult { - // This function mimics what Signal does in PushServiceSocket - val contentType = "application/octet-stream" - val file = DigestingRequestBody(attachment.data, attachment.outputStreamFactory, contentType, attachment.dataSize, attachment.listener) - Log.d("Loki", "File size: ${attachment.dataSize} bytes.") - val body = MultipartBody.Builder() - .setType(MultipartBody.FORM) - .addFormDataPart("type", "network.loki") - .addFormDataPart("Content-Type", contentType) - .addFormDataPart("content", UUID.randomUUID().toString(), file) - .build() - val request = Request.Builder().url("$server/files").post(body) - return upload(server, request) { json -> // Retrying is handled by AttachmentUploadJob - val data = json["data"] as? Map<*, *> - if (data == null) { - Log.e("Loki", "Couldn't parse attachment from: $json.") - throw Error.ParsingFailed - } - val id = data["id"] as? Long ?: (data["id"] as? Int)?.toLong() ?: (data["id"] as? String)?.toLong() - val url = data["url"] as? String - if (id == null || url == null || url.isEmpty()) { - Log.e("Loki", "Couldn't parse upload from: $json.") - throw Error.ParsingFailed - } - UploadResult(id, url, file.transmittedDigest) - }.get() - } - - @Throws(PushNetworkException::class, NonSuccessfulResponseCodeException::class) - fun uploadProfilePicture(server: String, key: ByteArray, profilePicture: StreamDetails, setLastProfilePictureUpload: () -> Unit): UploadResult { - val profilePictureUploadData = ProfileAvatarData(profilePicture.stream, ProfileCipherOutputStream.getCiphertextLength(profilePicture.length), profilePicture.contentType, ProfileCipherOutputStreamFactory(key)) - val file = DigestingRequestBody(profilePictureUploadData.data, profilePictureUploadData.outputStreamFactory, - profilePictureUploadData.contentType, profilePictureUploadData.dataLength, null) - val body = MultipartBody.Builder() - .setType(MultipartBody.FORM) - .addFormDataPart("type", "network.loki") - .addFormDataPart("Content-Type", "application/octet-stream") - .addFormDataPart("content", UUID.randomUUID().toString(), file) - .build() - val request = Request.Builder().url("$server/files").post(body) - return retryIfNeeded(4) { - upload(server, request) { json -> - val data = json["data"] as? Map<*, *> - if (data == null) { - Log.d("Loki", "Couldn't parse profile picture from: $json.") - throw Error.ParsingFailed - } - val id = data["id"] as? Long ?: (data["id"] as? Int)?.toLong() ?: (data["id"] as? String)?.toLong() - val url = data["url"] as? String - if (id == null || url == null || url.isEmpty()) { - Log.d("Loki", "Couldn't parse profile picture from: $json.") - throw Error.ParsingFailed - } - setLastProfilePictureUpload() - UploadResult(id, url, file.transmittedDigest) - } - }.get() - } - - @Throws(PushNetworkException::class, NonSuccessfulResponseCodeException::class) - private fun upload(server: String, request: Request.Builder, parse: (Map<*, *>) -> UploadResult): Promise { - val promise: Promise, Exception> - if (server == FileServerAPI.shared.server) { - request.addHeader("Authorization", "Bearer loki") - // Uploads to the Loki File Server shouldn't include any personally identifiable information, so use a dummy auth token - promise = OnionRequestAPI.sendOnionRequest(request.build(), FileServerAPI.shared.server, FileServerAPI.fileServerPublicKey) - } else { - promise = FileServerAPI.shared.getPublicKeyForOpenGroupServer(server).bind { openGroupServerPublicKey -> - getAuthToken(server).bind { token -> - request.addHeader("Authorization", "Bearer $token") - OnionRequestAPI.sendOnionRequest(request.build(), server, openGroupServerPublicKey) - } - } - } - return promise.map { json -> - parse(json) - }.recover { exception -> - if (exception is HTTP.HTTPRequestFailedException) { - val statusCode = exception.statusCode - if (statusCode == 401 || statusCode == 403) { - MessagingModuleConfiguration.shared.storage.setAuthToken(server, null) - } - throw NonSuccessfulResponseCodeException("Request returned with status code ${exception.statusCode}.") - } - throw PushNetworkException(exception) - } - } -} - -private fun Boolean.toInt(): Int { return if (this) 1 else 0 } diff --git a/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt b/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt index f3d062ab09..d2e33b64d9 100644 --- a/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt @@ -6,7 +6,7 @@ import nl.komponents.kovenant.deferred import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map import okhttp3.Request -import org.session.libsession.messaging.file_server.FileServerAPI +import org.session.libsession.messaging.file_server.FileServerAPIV2 import org.session.libsession.utilities.AESGCM import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Base64 @@ -307,7 +307,7 @@ object OnionRequestAPI { val url = "${guardSnode.address}:${guardSnode.port}/onion_req/v2" val finalEncryptionResult = result.finalEncryptionResult val onion = finalEncryptionResult.ciphertext - if (destination is Destination.Server && onion.count().toDouble() > 0.75 * FileServerAPI.maxFileSize.toDouble()) { + if (destination is Destination.Server && onion.count().toDouble() > 0.75 * FileServerAPIV2.maxFileSize.toDouble()) { Log.d("Loki", "Approaching request size limit: ~${onion.count()} bytes.") } @Suppress("NAME_SHADOWING") val parameters = mapOf( diff --git a/libsession/src/main/java/org/session/libsession/utilities/DownloadUtilities.kt b/libsession/src/main/java/org/session/libsession/utilities/DownloadUtilities.kt index 9aee38647e..8ccb659dbb 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/DownloadUtilities.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/DownloadUtilities.kt @@ -1,15 +1,9 @@ package org.session.libsession.utilities import okhttp3.HttpUrl -import okhttp3.Request -import org.session.libsession.messaging.file_server.FileServerAPI import org.session.libsession.messaging.file_server.FileServerAPIV2 -import org.session.libsession.snode.OnionRequestAPI import org.session.libsignal.utilities.Log import org.session.libsignal.messages.SignalServiceAttachment -import org.session.libsignal.exceptions.NonSuccessfulResponseCodeException -import org.session.libsignal.exceptions.PushNetworkException -import org.session.libsignal.utilities.Base64 import java.io.* object DownloadUtilities { @@ -18,14 +12,14 @@ object DownloadUtilities { * Blocks the calling thread. */ @JvmStatic - fun downloadFile(destination: File, url: String, maxSize: Int, listener: SignalServiceAttachment.ProgressListener?) { + fun downloadFile(destination: File, url: String) { val outputStream = FileOutputStream(destination) // Throws var remainingAttempts = 4 var exception: Exception? = null while (remainingAttempts > 0) { remainingAttempts -= 1 try { - downloadFile(outputStream, url, maxSize, listener) + downloadFile(outputStream, url) exception = null break } catch (e: Exception) { @@ -39,66 +33,16 @@ object DownloadUtilities { * Blocks the calling thread. */ @JvmStatic - fun downloadFile(outputStream: OutputStream, url: String, maxSize: Int, listener: SignalServiceAttachment.ProgressListener?) { - - if (url.contains(FileServerAPIV2.SERVER) || url.contains(FileServerAPIV2.OLD_SERVER)) { - val httpUrl = HttpUrl.parse(url)!! - val fileId = httpUrl.pathSegments().last() - val useOldServer = url.contains(FileServerAPIV2.OLD_SERVER) - try { - FileServerAPIV2.download(fileId.toLong(), useOldServer).get().let { - outputStream.write(it) - } - } catch (e: Exception) { - Log.e("Loki", "Couln't download attachment due to error",e) - throw e - } - } else { - // We need to throw a PushNetworkException or NonSuccessfulResponseCodeException - // because the underlying Signal logic requires these to work correctly - val oldPrefixedHost = "https://" + HttpUrl.get(url).host() - var newPrefixedHost = oldPrefixedHost - if (oldPrefixedHost.contains(FileServerAPI.fileStorageBucketURL)) { - newPrefixedHost = FileServerAPI.shared.server - } - // Edge case that needs to work: https://file-static.lokinet.org/i1pNmpInq3w9gF3TP8TFCa1rSo38J6UM - // → https://file.getsession.org/loki/v1/f/XLxogNXVEIWHk14NVCDeppzTujPHxu35 - val fileID = url.substringAfter(oldPrefixedHost).substringAfter("/f/") - val sanitizedURL = "$newPrefixedHost/loki/v1/f/$fileID" - val request = Request.Builder().url(sanitizedURL).get() - try { - val serverPublicKey = if (newPrefixedHost.contains(FileServerAPI.shared.server)) FileServerAPI.fileServerPublicKey - else FileServerAPI.shared.getPublicKeyForOpenGroupServer(newPrefixedHost).get() - val json = OnionRequestAPI.sendOnionRequest(request.build(), newPrefixedHost, serverPublicKey, isJSONRequired = false).get() - val result = json["result"] as? String - if (result == null) { - Log.d("Loki", "Couldn't parse attachment from: $json.") - throw PushNetworkException("Missing response body.") - } - val body = Base64.decode(result) - if (body.size > maxSize) { - Log.d("Loki", "Attachment size limit exceeded.") - throw PushNetworkException("Max response size exceeded.") - } - body.inputStream().use { input -> - val buffer = ByteArray(32768) - var count = 0 - var bytes = input.read(buffer) - while (bytes >= 0) { - outputStream.write(buffer, 0, bytes) - count += bytes - if (count > maxSize) { - Log.d("Loki", "Attachment size limit exceeded.") - throw PushNetworkException("Max response size exceeded.") - } - listener?.onAttachmentProgress(body.size.toLong(), count.toLong()) - bytes = input.read(buffer) - } - } - } catch (e: Exception) { - Log.e("Loki", "Couldn't download attachment due to error", e) - throw if (e is NonSuccessfulResponseCodeException) e else PushNetworkException(e) + fun downloadFile(outputStream: OutputStream, urlAsString: String) { + val url = HttpUrl.parse(urlAsString)!! + val fileID = url.pathSegments().last() + try { + FileServerAPIV2.download(fileID.toLong()).get().let { + outputStream.write(it) } + } catch (e: Exception) { + Log.e("Loki", "Couldn't download attachment.", e) + throw e } } } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/utilities/ProfilePictureUtilities.kt b/libsession/src/main/java/org/session/libsession/utilities/ProfilePictureUtilities.kt index df9802417f..47223c8096 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/ProfilePictureUtilities.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/ProfilePictureUtilities.kt @@ -36,7 +36,7 @@ object ProfilePictureUtilities { deferred.reject(e) } TextSecurePreferences.setLastProfilePictureUpload(context, Date().time) - val url = "${FileServerAPIV2.SERVER}/files/$id" + val url = "${FileServerAPIV2.server}/files/$id" TextSecurePreferences.setProfilePictureURL(context, url) deferred.resolve(Unit) } diff --git a/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt b/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt index 8cd7913655..63abd4b438 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt @@ -33,7 +33,6 @@ class SSKEnvironment( fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String) fun setProfileKey(context: Context, recipient: Recipient, profileKey: ByteArray) fun setUnidentifiedAccessMode(context: Context, recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode) - fun updateOpenGroupProfilePicturesIfNeeded(context: Context) } interface MessageExpirationManagerProtocol { diff --git a/libsession/src/main/java/org/session/libsession/utilities/UploadResult.kt b/libsession/src/main/java/org/session/libsession/utilities/UploadResult.kt new file mode 100644 index 0000000000..6be8b4437a --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/utilities/UploadResult.kt @@ -0,0 +1,3 @@ +package org.session.libsession.utilities + +data class UploadResult(val id: Long, val url: String, val digest: ByteArray?) \ No newline at end of file diff --git a/libsignal/src/main/java/org/session/libsignal/database/LokiUserDatabaseProtocol.kt b/libsignal/src/main/java/org/session/libsignal/database/LokiUserDatabaseProtocol.kt index 1f57a6ff51..e22ecb1059 100644 --- a/libsignal/src/main/java/org/session/libsignal/database/LokiUserDatabaseProtocol.kt +++ b/libsignal/src/main/java/org/session/libsignal/database/LokiUserDatabaseProtocol.kt @@ -3,6 +3,5 @@ package org.session.libsignal.database interface LokiUserDatabaseProtocol { fun getDisplayName(publicKey: String): String? - fun getServerDisplayName(serverID: String, publicKey: String): String? fun getProfilePictureURL(publicKey: String): String? }