mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
Merge remote-tracking branch 'upstream/dev' into feature/unread-mention-indicator
# Conflicts: # app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt # libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt
This commit is contained in:
commit
dc9458f313
@ -47,6 +47,7 @@ import org.session.libsession.utilities.Util;
|
||||
import org.session.libsession.utilities.WindowDebouncer;
|
||||
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||
import org.session.libsession.utilities.dynamiclanguage.LocaleParser;
|
||||
import org.session.libsignal.utilities.HTTP;
|
||||
import org.session.libsignal.utilities.JsonUtil;
|
||||
import org.session.libsignal.utilities.Log;
|
||||
import org.session.libsignal.utilities.ThreadUtils;
|
||||
@ -67,6 +68,7 @@ import org.thoughtcrime.securesms.groups.OpenGroupMigrator;
|
||||
import org.thoughtcrime.securesms.home.HomeActivity;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.jobs.FastJobStorage;
|
||||
import org.thoughtcrime.securesms.jobs.JobManagerFactories;
|
||||
import org.thoughtcrime.securesms.logging.AndroidLogger;
|
||||
@ -237,6 +239,9 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
||||
resubmitProfilePictureIfNeeded();
|
||||
loadEmojiSearchIndexIfNeeded();
|
||||
EmojiSource.refresh();
|
||||
|
||||
NetworkConstraint networkConstraint = new NetworkConstraint.Factory(this).create();
|
||||
HTTP.INSTANCE.setConnectedToNetwork(networkConstraint::isMet);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -176,6 +176,11 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
|
||||
return messageDB.getMessageID(serverId, threadId)
|
||||
}
|
||||
|
||||
override fun getMessageIDs(serverIds: List<Long>, threadId: Long): Pair<List<Long>, List<Long>> {
|
||||
val messageDB = DatabaseComponent.get(context).lokiMessageDatabase()
|
||||
return messageDB.getMessageIDs(serverIds, threadId)
|
||||
}
|
||||
|
||||
override fun deleteMessage(messageID: Long, isSms: Boolean) {
|
||||
val messagingDatabase: MessagingDatabase = if (isSms) DatabaseComponent.get(context).smsDatabase()
|
||||
else DatabaseComponent.get(context).mmsDatabase()
|
||||
@ -184,6 +189,15 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
|
||||
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHash(messageID)
|
||||
}
|
||||
|
||||
override fun deleteMessages(messageIDs: List<Long>, threadId: Long, isSms: Boolean) {
|
||||
val messagingDatabase: MessagingDatabase = if (isSms) DatabaseComponent.get(context).smsDatabase()
|
||||
else DatabaseComponent.get(context).mmsDatabase()
|
||||
|
||||
messagingDatabase.deleteMessages(messageIDs.toLongArray(), threadId)
|
||||
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessages(messageIDs)
|
||||
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHashes(messageIDs)
|
||||
}
|
||||
|
||||
override fun updateMessageAsDeleted(timestamp: Long, author: String): Long? {
|
||||
val database = DatabaseComponent.get(context).mmsSmsDatabase()
|
||||
val address = Address.fromSerialized(author)
|
||||
|
@ -13,6 +13,11 @@ class ScreenshotObserver(private val context: Context, handler: Handler, private
|
||||
override fun onChange(selfChange: Boolean, uri: Uri?) {
|
||||
super.onChange(selfChange, uri)
|
||||
uri ?: return
|
||||
|
||||
// There is an odd bug where we can get notified for changes to 'content://media/external'
|
||||
// directly which is a protected folder, this code is to prevent that crash
|
||||
if (uri.scheme == "content" && uri.host == "media" && uri.path == "/external") { return }
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
queryRelativeDataColumn(uri)
|
||||
} else {
|
||||
|
@ -315,11 +315,24 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
restoreDraftIfNeeded()
|
||||
setUpUiStateObserver()
|
||||
binding!!.scrollToBottomButton.setOnClickListener {
|
||||
val layoutManager = binding?.conversationRecyclerView?.layoutManager ?: return@setOnClickListener
|
||||
val layoutManager = (binding?.conversationRecyclerView?.layoutManager as? LinearLayoutManager) ?: return@setOnClickListener
|
||||
|
||||
if (layoutManager.isSmoothScrolling) {
|
||||
binding?.conversationRecyclerView?.scrollToPosition(0)
|
||||
} else {
|
||||
binding?.conversationRecyclerView?.smoothScrollToPosition(0)
|
||||
// It looks like 'smoothScrollToPosition' will actually load all intermediate items in
|
||||
// order to do the scroll, this can be very slow if there are a lot of messages so
|
||||
// instead we check the current position and if there are more than 10 items to scroll
|
||||
// we jump instantly to the 10th item and scroll from there (this should happen quick
|
||||
// enough to give a similar scroll effect without having to load everything)
|
||||
val position = layoutManager.findFirstVisibleItemPosition()
|
||||
if (position > 10) {
|
||||
binding?.conversationRecyclerView?.scrollToPosition(10)
|
||||
}
|
||||
|
||||
binding?.conversationRecyclerView?.post {
|
||||
binding?.conversationRecyclerView?.smoothScrollToPosition(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
unreadCount = mmsSmsDb.getUnreadCount(viewModel.threadId)
|
||||
|
@ -35,6 +35,7 @@ import com.bumptech.glide.Glide;
|
||||
|
||||
import net.zetetic.database.sqlcipher.SQLiteDatabase;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
|
||||
@ -318,6 +319,28 @@ public class AttachmentDatabase extends Database {
|
||||
notifyAttachmentListeners();
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
void deleteAttachmentsForMessages(long[] mmsIds) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
Cursor cursor = null;
|
||||
String mmsIdString = StringUtils.join(mmsIds, ',');
|
||||
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, new String[] {DATA, THUMBNAIL, CONTENT_TYPE}, MMS_ID + " IN (?)",
|
||||
new String[] {mmsIdString}, null, null, null);
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
deleteAttachmentOnDisk(cursor.getString(0), cursor.getString(1), cursor.getString(2));
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
database.delete(TABLE_NAME, MMS_ID + " IN (?)", new String[] {mmsIdString});
|
||||
notifyAttachmentListeners();
|
||||
}
|
||||
|
||||
public void deleteAttachment(@NonNull AttachmentId id) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
|
||||
|
@ -318,6 +318,19 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
|
||||
notifyConversationListListeners();
|
||||
}
|
||||
|
||||
public boolean hasDownloadedProfilePicture(String groupId) {
|
||||
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[]{AVATAR}, GROUP_ID + " = ?",
|
||||
new String[] {groupId},
|
||||
null, null, null))
|
||||
{
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
return !cursor.isNull(0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void updateMembers(String groupId, List<Address> members) {
|
||||
Collections.sort(members);
|
||||
|
||||
|
@ -8,6 +8,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import net.zetetic.database.sqlcipher.SQLiteDatabase;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.session.libsession.utilities.Address;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
|
||||
@ -109,6 +110,11 @@ public class GroupReceiptDatabase extends Database {
|
||||
db.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {String.valueOf(mmsId)});
|
||||
}
|
||||
|
||||
void deleteRowsForMessages(long[] mmsIds) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
db.delete(TABLE_NAME, MMS_ID + " IN (?)", new String[] {StringUtils.join(mmsIds, ',')});
|
||||
}
|
||||
|
||||
void deleteAllRows() {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
db.delete(TABLE_NAME, null, null);
|
||||
|
@ -77,6 +77,25 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
||||
database.endTransaction()
|
||||
}
|
||||
|
||||
fun deleteMessages(messageIDs: List<Long>) {
|
||||
val database = databaseHelper.writableDatabase
|
||||
database.beginTransaction()
|
||||
|
||||
database.delete(
|
||||
messageIDTable,
|
||||
"${Companion.messageID} IN (${messageIDs.map { "?" }.joinToString(",")})",
|
||||
messageIDs.map { "$it" }.toTypedArray()
|
||||
)
|
||||
database.delete(
|
||||
messageThreadMappingTable,
|
||||
"${Companion.messageID} IN (${messageIDs.map { "?" }.joinToString(",")})",
|
||||
messageIDs.map { "$it" }.toTypedArray()
|
||||
)
|
||||
|
||||
database.setTransactionSuccessful()
|
||||
database.endTransaction()
|
||||
}
|
||||
|
||||
/**
|
||||
* @return pair of sms or mms table-specific ID and whether it is in SMS table
|
||||
*/
|
||||
@ -96,6 +115,37 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
||||
}
|
||||
}
|
||||
|
||||
fun getMessageIDs(serverIDs: List<Long>, threadID: Long): Pair<List<Long>, List<Long>> {
|
||||
val database = databaseHelper.readableDatabase
|
||||
|
||||
// Retrieve the message ids
|
||||
val messageIdCursor = database
|
||||
.rawQuery(
|
||||
"""
|
||||
SELECT ${messageThreadMappingTable}.${messageID}, ${messageIDTable}.${messageType}
|
||||
FROM ${messageThreadMappingTable}
|
||||
JOIN ${messageIDTable} ON ${messageIDTable}.message_id = ${messageThreadMappingTable}.${messageID}
|
||||
WHERE (
|
||||
${messageThreadMappingTable}.${Companion.threadID} = $threadID AND
|
||||
${messageThreadMappingTable}.${Companion.serverID} IN (${serverIDs.joinToString(",")})
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
val smsMessageIds: MutableList<Long> = mutableListOf()
|
||||
val mmsMessageIds: MutableList<Long> = mutableListOf()
|
||||
while (messageIdCursor.moveToNext()) {
|
||||
if (messageIdCursor.getInt(1) == SMS_TYPE) {
|
||||
smsMessageIds.add(messageIdCursor.getLong(0))
|
||||
}
|
||||
else {
|
||||
mmsMessageIds.add(messageIdCursor.getLong(0))
|
||||
}
|
||||
}
|
||||
|
||||
return Pair(smsMessageIds, mmsMessageIds)
|
||||
}
|
||||
|
||||
override fun setServerID(messageID: Long, serverID: Long, isSms: Boolean) {
|
||||
val database = databaseHelper.writableDatabase
|
||||
val contentValues = ContentValues(3)
|
||||
@ -183,6 +233,15 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
||||
database.delete(messageHashTable, "${Companion.messageID} = ?", arrayOf(messageID.toString()))
|
||||
}
|
||||
|
||||
fun deleteMessageServerHashes(messageIDs: List<Long>) {
|
||||
val database = databaseHelper.writableDatabase
|
||||
database.delete(
|
||||
messageHashTable,
|
||||
"${Companion.messageID} IN (${messageIDs.map { "?" }.joinToString(",")})",
|
||||
messageIDs.map { "$it" }.toTypedArray()
|
||||
)
|
||||
}
|
||||
|
||||
fun migrateThreadId(legacyThreadId: Long, newThreadId: Long) {
|
||||
val database = databaseHelper.writableDatabase
|
||||
val contentValues = ContentValues(1)
|
||||
|
@ -42,6 +42,7 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn
|
||||
public abstract void markAsDeleted(long messageId, boolean read, boolean hasMention);
|
||||
|
||||
public abstract boolean deleteMessage(long messageId);
|
||||
public abstract boolean deleteMessages(long[] messageId, long threadId);
|
||||
|
||||
public abstract void updateThreadId(long fromId, long toId);
|
||||
|
||||
|
@ -999,6 +999,23 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
||||
return threadDeleted
|
||||
}
|
||||
|
||||
override fun deleteMessages(messageIds: LongArray, threadId: Long): Boolean {
|
||||
val attachmentDatabase = get(context).attachmentDatabase()
|
||||
val groupReceiptDatabase = get(context).groupReceiptDatabase()
|
||||
|
||||
queue(Runnable { attachmentDatabase.deleteAttachmentsForMessages(messageIds) })
|
||||
groupReceiptDatabase.deleteRowsForMessages(messageIds)
|
||||
|
||||
val database = databaseHelper.writableDatabase
|
||||
database!!.delete(TABLE_NAME, ID_IN, arrayOf(messageIds.joinToString(",")))
|
||||
|
||||
val threadDeleted = get(context).threadDatabase().update(threadId, false)
|
||||
notifyConversationListeners(threadId)
|
||||
notifyStickerListeners()
|
||||
notifyStickerPackListeners()
|
||||
return threadDeleted
|
||||
}
|
||||
|
||||
override fun updateThreadId(fromId: Long, toId: Long) {
|
||||
val contentValues = ContentValues(1)
|
||||
contentValues.put(THREAD_ID, toId)
|
||||
|
@ -31,6 +31,7 @@ import com.annimon.stream.Stream;
|
||||
import net.zetetic.database.sqlcipher.SQLiteDatabase;
|
||||
import net.zetetic.database.sqlcipher.SQLiteStatement;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.session.libsession.messaging.calls.CallMessageType;
|
||||
import org.session.libsession.messaging.messages.signal.IncomingGroupMessage;
|
||||
import org.session.libsession.messaging.messages.signal.IncomingTextMessage;
|
||||
@ -52,6 +53,7 @@ import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -603,6 +605,30 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
return threadDeleted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteMessages(long[] messageIds, long threadId) {
|
||||
String[] argsArray = new String[messageIds.length];
|
||||
String[] argValues = new String[messageIds.length];
|
||||
Arrays.fill(argsArray, "?");
|
||||
|
||||
for (int i = 0; i < messageIds.length; i++) {
|
||||
argValues[i] = (messageIds[i] + "");
|
||||
}
|
||||
|
||||
String combinedMessageIdArgss = StringUtils.join(messageIds, ',');
|
||||
String combinedMessageIds = StringUtils.join(messageIds, ',');
|
||||
Log.i("MessageDatabase", "Deleting: " + combinedMessageIds);
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
db.delete(
|
||||
TABLE_NAME,
|
||||
ID + " IN (" + StringUtils.join(argsArray, ',') + ")",
|
||||
argValues
|
||||
);
|
||||
boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false);
|
||||
notifyConversationListeners(threadId);
|
||||
return threadDeleted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateThreadId(long fromId, long toId) {
|
||||
ContentValues contentValues = new ContentValues(1);
|
||||
|
@ -321,6 +321,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
DatabaseComponent.get(context).groupDatabase().updateProfilePicture(groupID, newValue)
|
||||
}
|
||||
|
||||
override fun hasDownloadedProfilePicture(groupID: String): Boolean {
|
||||
return DatabaseComponent.get(context).groupDatabase().hasDownloadedProfilePicture(groupID)
|
||||
}
|
||||
|
||||
override fun getReceivedMessageTimestamps(): Set<Long> {
|
||||
return SessionMetaProtocol.getTimestamps()
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
package org.thoughtcrime.securesms.webrtc
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.telephony.TelephonyManager
|
||||
import androidx.core.content.ContextCompat
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.serialization.json.Json
|
||||
@ -176,8 +178,22 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
||||
_callStateEvents.value = newState
|
||||
}
|
||||
|
||||
fun isBusy(context: Context, callId: UUID) = callId != this.callId && (currentConnectionState != CallState.Idle
|
||||
|| context.getSystemService(TelephonyManager::class.java).callState != TelephonyManager.CALL_STATE_IDLE)
|
||||
fun isBusy(context: Context, callId: UUID): Boolean {
|
||||
// Make sure we have the permission before accessing the callState
|
||||
if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
|
||||
return (
|
||||
callId != this.callId && (
|
||||
currentConnectionState != CallState.Idle ||
|
||||
context.getSystemService(TelephonyManager::class.java).callState != TelephonyManager.CALL_STATE_IDLE
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
callId != this.callId &&
|
||||
currentConnectionState != CallState.Idle
|
||||
)
|
||||
}
|
||||
|
||||
fun isPreOffer() = currentConnectionState == CallState.RemotePreOffer
|
||||
|
||||
|
@ -12,6 +12,7 @@ import org.session.libsession.database.StorageProtocol
|
||||
import org.session.libsession.messaging.calls.CallMessageType
|
||||
import org.session.libsession.messaging.messages.control.CallMessage
|
||||
import org.session.libsession.messaging.utilities.WebRtcUtils
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
@ -29,6 +30,10 @@ import org.webrtc.IceCandidate
|
||||
|
||||
class CallMessageProcessor(private val context: Context, private val textSecurePreferences: TextSecurePreferences, lifecycle: Lifecycle, private val storage: StorageProtocol) {
|
||||
|
||||
companion object {
|
||||
private const val VERY_EXPIRED_TIME = 15 * 60 * 1000L
|
||||
}
|
||||
|
||||
init {
|
||||
lifecycle.coroutineScope.launch(IO) {
|
||||
while (isActive) {
|
||||
@ -53,6 +58,13 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
val isVeryExpired = (nextMessage.sentTimestamp?:0) + VERY_EXPIRED_TIME < SnodeAPI.nowWithOffset
|
||||
if (isVeryExpired) {
|
||||
Log.e("Loki", "Dropping very expired call message")
|
||||
continue
|
||||
}
|
||||
|
||||
when (nextMessage.type) {
|
||||
OFFER -> incomingCall(nextMessage)
|
||||
ANSWER -> incomingAnswer(nextMessage)
|
||||
@ -78,7 +90,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
|
||||
private fun incomingHangup(callMessage: CallMessage) {
|
||||
val callId = callMessage.callId ?: return
|
||||
val hangupIntent = WebRtcCallService.remoteHangupIntent(context, callId)
|
||||
ContextCompat.startForegroundService(context, hangupIntent)
|
||||
context.startService(hangupIntent)
|
||||
}
|
||||
|
||||
private fun incomingAnswer(callMessage: CallMessage) {
|
||||
@ -91,7 +103,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
|
||||
sdp = sdp,
|
||||
callId = callId
|
||||
)
|
||||
ContextCompat.startForegroundService(context, answerIntent)
|
||||
context.startService(answerIntent)
|
||||
}
|
||||
|
||||
private fun handleIceCandidates(callMessage: CallMessage) {
|
||||
@ -120,7 +132,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
|
||||
callId = callId,
|
||||
callTime = callMessage.sentTimestamp!!
|
||||
)
|
||||
ContextCompat.startForegroundService(context, incomingIntent)
|
||||
context.startService(incomingIntent)
|
||||
}
|
||||
|
||||
private fun incomingCall(callMessage: CallMessage) {
|
||||
@ -134,8 +146,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP
|
||||
callId = callId,
|
||||
callTime = callMessage.sentTimestamp!!
|
||||
)
|
||||
ContextCompat.startForegroundService(context, incomingIntent)
|
||||
|
||||
context.startService(incomingIntent)
|
||||
}
|
||||
|
||||
private fun CallMessage.iceCandidates(): List<IceCandidate> {
|
||||
|
@ -865,5 +865,5 @@
|
||||
<string name="new_conversation_dialog_back_button_content_description">Navigate Back</string>
|
||||
<string name="new_conversation_dialog_close_button_content_description">Close Dialog</string>
|
||||
<string name="ErrorNotifier_migration">Database Upgrade Failed</string>
|
||||
<string name="ErrorNotifier_migration_downgrade">Please contact support to report the error and then install an older version to continue using Session.</string>
|
||||
<string name="ErrorNotifier_migration_downgrade">Please contact support to report the error.</string>
|
||||
</resources>
|
||||
|
@ -20,7 +20,9 @@ interface MessageDataProvider {
|
||||
* @return pair of sms or mms table-specific ID and whether it is in SMS table
|
||||
*/
|
||||
fun getMessageID(serverId: Long, threadId: Long): Pair<Long, Boolean>?
|
||||
fun getMessageIDs(serverIDs: List<Long>, threadID: Long): Pair<List<Long>, List<Long>>
|
||||
fun deleteMessage(messageID: Long, isSms: Boolean)
|
||||
fun deleteMessages(messageIDs: List<Long>, threadId: Long, isSms: Boolean)
|
||||
fun updateMessageAsDeleted(timestamp: Long, author: String): Long?
|
||||
fun getServerHashForMessage(messageID: Long): String?
|
||||
fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment?
|
||||
|
@ -81,6 +81,7 @@ interface StorageProtocol {
|
||||
// Open Group Metadata
|
||||
fun updateTitle(groupID: String, newValue: String)
|
||||
fun updateProfilePicture(groupID: String, newValue: ByteArray)
|
||||
fun hasDownloadedProfilePicture(groupID: String): Boolean
|
||||
fun setUserCount(room: String, server: String, newValue: Int)
|
||||
|
||||
// Last Message Server ID
|
||||
|
@ -77,7 +77,11 @@ object FileServerApi {
|
||||
OnionRequestAPI.sendOnionRequest(requestBuilder.build(), server, serverPublicKey).map {
|
||||
it.body ?: throw Error.ParsingFailed
|
||||
}.fail { e ->
|
||||
Log.e("Loki", "File server request failed.", e)
|
||||
when (e) {
|
||||
// No need for the stack trace for HTTP errors
|
||||
is HTTP.HTTPRequestFailedException -> Log.e("Loki", "File server request failed due to error: ${e.message}")
|
||||
else -> Log.e("Loki", "File server request failed", e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Promise.ofFail(IllegalStateException("It's currently not allowed to send non onion routed requests."))
|
||||
|
@ -92,12 +92,23 @@ class BatchMessageReceiveJob(
|
||||
threadMap[threadID]!! += parsedParams
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Couldn't receive message.", e)
|
||||
if (e is MessageReceiver.Error && !e.isRetryable) {
|
||||
Log.e(TAG, "Message failed permanently",e)
|
||||
} else {
|
||||
Log.e(TAG, "Message failed",e)
|
||||
failures += messageParameters
|
||||
when (e) {
|
||||
is MessageReceiver.Error.DuplicateMessage, MessageReceiver.Error.SelfSend -> {
|
||||
Log.i(TAG, "Couldn't receive message, failed with error: ${e.message}")
|
||||
}
|
||||
is MessageReceiver.Error -> {
|
||||
if (!e.isRetryable) {
|
||||
Log.e(TAG, "Couldn't receive message, failed permanently", e)
|
||||
}
|
||||
else {
|
||||
Log.e(TAG, "Couldn't receive message, failed", e)
|
||||
failures += messageParameters
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Log.e(TAG, "Couldn't receive message, failed", e)
|
||||
failures += messageParameters
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class JobQueue : JobDelegate {
|
||||
private val jobTimestampMap = ConcurrentHashMap<Long, AtomicInteger>()
|
||||
private val rxDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
||||
private val rxMediaDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
|
||||
private val openGroupDispatcher = Executors.newCachedThreadPool().asCoroutineDispatcher()
|
||||
private val openGroupDispatcher = Executors.newFixedThreadPool(8).asCoroutineDispatcher()
|
||||
private val txDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
||||
private val scope = CoroutineScope(Dispatchers.Default) + SupervisorJob()
|
||||
private val queue = Channel<Job>(UNLIMITED)
|
||||
|
@ -11,6 +11,7 @@ import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||
import org.session.libsession.messaging.utilities.Data
|
||||
import org.session.libsession.snode.OnionRequestAPI
|
||||
import org.session.libsignal.utilities.HTTP
|
||||
import org.session.libsignal.utilities.Log
|
||||
|
||||
class MessageSendJob(val message: Message, val destination: Destination) : Job {
|
||||
@ -67,14 +68,25 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
|
||||
val promise = MessageSender.send(this.message, this.destination).success {
|
||||
this.handleSuccess()
|
||||
}.fail { exception ->
|
||||
Log.e(TAG, "Couldn't send message due to error: $exception.")
|
||||
if (exception is MessageSender.Error) {
|
||||
if (!exception.isRetryable) { this.handlePermanentFailure(exception) }
|
||||
var logStacktrace = true
|
||||
|
||||
when (exception) {
|
||||
// No need for the stack trace for HTTP errors
|
||||
is HTTP.HTTPRequestFailedException -> {
|
||||
logStacktrace = false
|
||||
|
||||
if (exception.statusCode == 429) { this.handlePermanentFailure(exception) }
|
||||
else { this.handleFailure(exception) }
|
||||
}
|
||||
is MessageSender.Error -> {
|
||||
if (!exception.isRetryable) { this.handlePermanentFailure(exception) }
|
||||
else { this.handleFailure(exception) }
|
||||
}
|
||||
else -> this.handleFailure(exception)
|
||||
}
|
||||
if (exception is OnionRequestAPI.HTTPRequestFailedAtDestinationException && exception.statusCode == 429) {
|
||||
this.handlePermanentFailure(exception)
|
||||
}
|
||||
this.handleFailure(exception)
|
||||
|
||||
if (logStacktrace) { Log.e(TAG, "Couldn't send message due to error", exception) }
|
||||
else { Log.e(TAG, "Couldn't send message due to error: ${exception.message}") }
|
||||
}
|
||||
try {
|
||||
promise.get()
|
||||
|
@ -23,14 +23,27 @@ class OpenGroupDeleteJob(private val messageServerIds: LongArray, private val th
|
||||
val dataProvider = MessagingModuleConfiguration.shared.messageDataProvider
|
||||
val numberToDelete = messageServerIds.size
|
||||
Log.d(TAG, "Deleting $numberToDelete messages")
|
||||
var numberDeleted = 0
|
||||
messageServerIds.forEach { serverId ->
|
||||
val (messageId, isSms) = dataProvider.getMessageID(serverId, threadId) ?: return@forEach
|
||||
dataProvider.deleteMessage(messageId, isSms)
|
||||
numberDeleted++
|
||||
|
||||
// FIXME: This entire process should probably run in a transaction (with the attachment deletion happening only if it succeeded)
|
||||
try {
|
||||
val messageIds = dataProvider.getMessageIDs(messageServerIds.toList(), threadId)
|
||||
|
||||
// Delete the SMS messages
|
||||
if (messageIds.first.isNotEmpty()) {
|
||||
dataProvider.deleteMessages(messageIds.first, threadId, true)
|
||||
}
|
||||
|
||||
// Delete the MMS messages
|
||||
if (messageIds.second.isNotEmpty()) {
|
||||
dataProvider.deleteMessages(messageIds.second, threadId, false)
|
||||
}
|
||||
|
||||
Log.d(TAG, "Deleted ${messageIds.first.size + messageIds.second.size} messages successfully")
|
||||
delegate?.handleJobSucceeded(this)
|
||||
}
|
||||
catch (e: Exception) {
|
||||
delegate?.handleJobFailed(this, e)
|
||||
}
|
||||
Log.d(TAG, "Deleted $numberDeleted messages successfully")
|
||||
delegate?.handleJobSucceeded(this)
|
||||
}
|
||||
|
||||
override fun serialize(): Data = Data.Builder()
|
||||
|
@ -383,7 +383,11 @@ object OpenGroupApi {
|
||||
}
|
||||
return if (request.useOnionRouting) {
|
||||
OnionRequestAPI.sendOnionRequest(requestBuilder.build(), request.server, publicKey).fail { e ->
|
||||
Log.e("SOGS", "Failed onion request", e)
|
||||
when (e) {
|
||||
// No need for the stack trace for HTTP errors
|
||||
is HTTP.HTTPRequestFailedException -> Log.e("SOGS", "Failed onion request: ${e.message}")
|
||||
else -> Log.e("SOGS", "Failed onion request", e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Promise.ofFail(IllegalStateException("It's currently not allowed to send non onion routed requests."))
|
||||
|
@ -117,6 +117,7 @@ class OpenGroupPoller(private val server: String, private val executorService: S
|
||||
) {
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val groupId = "$server.$roomToken"
|
||||
val dbGroupId = GroupUtil.getEncodedOpenGroupID(groupId.toByteArray())
|
||||
|
||||
val existingOpenGroup = storage.getOpenGroup(roomToken, server)
|
||||
val publicKey = existingOpenGroup?.publicKey ?: return
|
||||
@ -157,8 +158,19 @@ class OpenGroupPoller(private val server: String, private val executorService: S
|
||||
})
|
||||
}
|
||||
|
||||
// Start downloading the room image (if we don't have one or it's been updated)
|
||||
if (pollInfo.details?.imageId != null && pollInfo.details.imageId != existingOpenGroup.imageId) {
|
||||
if (
|
||||
(
|
||||
pollInfo.details != null &&
|
||||
pollInfo.details.imageId != null && (
|
||||
pollInfo.details.imageId != existingOpenGroup.imageId ||
|
||||
!storage.hasDownloadedProfilePicture(dbGroupId)
|
||||
)
|
||||
) || (
|
||||
pollInfo.details == null &&
|
||||
existingOpenGroup.imageId != null &&
|
||||
!storage.hasDownloadedProfilePicture(dbGroupId)
|
||||
)
|
||||
) {
|
||||
JobQueue.shared.add(GroupAvatarDownloadJob(roomToken, server))
|
||||
}
|
||||
}
|
||||
|
@ -78,8 +78,8 @@ object OnionRequestAPI {
|
||||
// endregion
|
||||
|
||||
class HTTPRequestFailedBlindingRequiredException(statusCode: Int, json: Map<*, *>, destination: String): HTTPRequestFailedAtDestinationException(statusCode, json, destination)
|
||||
open class HTTPRequestFailedAtDestinationException(val statusCode: Int, val json: Map<*, *>, val destination: String)
|
||||
: Exception("HTTP request failed at destination ($destination) with status code $statusCode.")
|
||||
open class HTTPRequestFailedAtDestinationException(statusCode: Int, json: Map<*, *>, val destination: String)
|
||||
: HTTP.HTTPRequestFailedException(statusCode, json, "HTTP request failed at destination ($destination) with status code $statusCode.")
|
||||
class InsufficientSnodesException : Exception("Couldn't find enough snodes to build a path.")
|
||||
|
||||
private data class OnionBuildingResult(
|
||||
|
@ -56,6 +56,10 @@ object SnodeAPI {
|
||||
* user's clock is incorrect.
|
||||
*/
|
||||
internal var clockOffset = 0L
|
||||
|
||||
val nowWithOffset
|
||||
get() = System.currentTimeMillis() + clockOffset
|
||||
|
||||
internal var forkInfo by observable(database.getForkInfo()) { _, oldValue, newValue ->
|
||||
if (newValue > oldValue) {
|
||||
Log.d("Loki", "Setting new fork info new: $newValue, old: $oldValue")
|
||||
|
@ -2,6 +2,7 @@ package org.session.libsession.utilities
|
||||
|
||||
import okhttp3.HttpUrl
|
||||
import org.session.libsession.messaging.file_server.FileServerApi
|
||||
import org.session.libsignal.utilities.HTTP
|
||||
import org.session.libsignal.utilities.Log
|
||||
import java.io.*
|
||||
|
||||
@ -40,7 +41,11 @@ object DownloadUtilities {
|
||||
outputStream.write(it)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Loki", "Couldn't download attachment.", e)
|
||||
when (e) {
|
||||
// No need for the stack trace for HTTP errors
|
||||
is HTTP.HTTPRequestFailedException -> Log.e("Loki", "Couldn't download attachment due to error: ${e.message}")
|
||||
else -> Log.e("Loki", "Couldn't download attachment", e)
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
object HTTP {
|
||||
var isConnectedToNetwork: (() -> Boolean) = { false }
|
||||
|
||||
private val seedNodeConnection by lazy {
|
||||
OkHttpClient().newBuilder()
|
||||
@ -64,8 +65,12 @@ object HTTP {
|
||||
|
||||
private const val timeout: Long = 120
|
||||
|
||||
class HTTPRequestFailedException(val statusCode: Int, val json: Map<*, *>?)
|
||||
: kotlin.Exception("HTTP request failed with status code $statusCode.")
|
||||
open class HTTPRequestFailedException(
|
||||
val statusCode: Int,
|
||||
val json: Map<*, *>?,
|
||||
message: String = "HTTP request failed with status code $statusCode"
|
||||
) : kotlin.Exception(message)
|
||||
class HTTPNoNetworkException : HTTPRequestFailedException(0, null, "No network connection")
|
||||
|
||||
enum class Verb(val rawValue: String) {
|
||||
GET("GET"), PUT("PUT"), POST("POST"), DELETE("DELETE")
|
||||
@ -120,8 +125,11 @@ object HTTP {
|
||||
response = connection.newCall(request.build()).execute()
|
||||
} catch (exception: Exception) {
|
||||
Log.d("Loki", "${verb.rawValue} request to $url failed due to error: ${exception.localizedMessage}.")
|
||||
|
||||
if (!isConnectedToNetwork()) { throw HTTPNoNetworkException() }
|
||||
|
||||
// Override the actual error so that we can correctly catch failed requests in OnionRequestAPI
|
||||
throw HTTPRequestFailedException(0, null)
|
||||
throw HTTPRequestFailedException(0, null, "HTTP request failed due to: ${exception.message}")
|
||||
}
|
||||
return when (val statusCode = response.code()) {
|
||||
200 -> {
|
||||
|
Loading…
Reference in New Issue
Block a user