mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-25 11:05:25 +00:00
Getting rid of .get call on promise
This commit is contained in:
parent
45a66d0eea
commit
3faae5ddbe
@ -115,6 +115,7 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import dagger.hilt.EntryPoints;
|
import dagger.hilt.EntryPoints;
|
||||||
import dagger.hilt.android.HiltAndroidApp;
|
import dagger.hilt.android.HiltAndroidApp;
|
||||||
|
import kotlin.Unit;
|
||||||
import network.loki.messenger.BuildConfig;
|
import network.loki.messenger.BuildConfig;
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
@ -307,6 +308,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
|||||||
startPollingIfNeeded();
|
startPollingIfNeeded();
|
||||||
|
|
||||||
OpenGroupManager.INSTANCE.startPolling();
|
OpenGroupManager.INSTANCE.startPolling();
|
||||||
|
return Unit.INSTANCE;
|
||||||
});
|
});
|
||||||
|
|
||||||
// fetch last version data
|
// fetch last version data
|
||||||
|
@ -6,7 +6,6 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import java.util.concurrent.TimeoutException
|
import java.util.concurrent.TimeoutException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
@ -14,12 +13,12 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
|
import org.session.libsession.snode.utilities.await
|
||||||
import org.session.libsignal.utilities.PublicKeyValidation
|
import org.session.libsignal.utilities.PublicKeyValidation
|
||||||
import org.session.libsignal.utilities.timeout
|
|
||||||
import org.thoughtcrime.securesms.ui.GetString
|
import org.thoughtcrime.securesms.ui.GetString
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
@ -68,12 +67,14 @@ internal class NewMessageViewModel @Inject constructor(
|
|||||||
// This could be an ONS name
|
// This could be an ONS name
|
||||||
_state.update { it.copy(isTextErrorColor = false, error = null, loading = true) }
|
_state.update { it.copy(isTextErrorColor = false, error = null, loading = true) }
|
||||||
|
|
||||||
loadOnsJob = viewModelScope.launch(Dispatchers.IO) {
|
loadOnsJob = viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
val publicKey = SnodeAPI.getAccountID(ons).timeout(30_000).get()
|
val publicKey = withTimeout(30_000L, {
|
||||||
if (isActive) onPublicKey(publicKey)
|
SnodeAPI.getAccountID(ons).await()
|
||||||
|
})
|
||||||
|
onPublicKey(publicKey)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (isActive) onError(e)
|
onError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,11 @@ import android.text.SpannableStringBuilder
|
|||||||
import android.text.style.StyleSpan
|
import android.text.style.StyleSpan
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.squareup.phrase.Phrase
|
import com.squareup.phrase.Phrase
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.database.StorageProtocol
|
import org.session.libsession.database.StorageProtocol
|
||||||
import org.session.libsession.utilities.OpenGroupUrlParser
|
import org.session.libsession.utilities.OpenGroupUrlParser
|
||||||
@ -43,14 +47,23 @@ class JoinOpenGroupDialog(private val name: String, private val url: String) : D
|
|||||||
private fun join() {
|
private fun join() {
|
||||||
val openGroup = OpenGroupUrlParser.parseUrl(url)
|
val openGroup = OpenGroupUrlParser.parseUrl(url)
|
||||||
val activity = requireActivity()
|
val activity = requireActivity()
|
||||||
ThreadUtils.queue {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
openGroup.apply { OpenGroupManager.add(server, room, serverPublicKey, activity) }
|
withContext(Dispatchers.Default) {
|
||||||
storage.onOpenGroupAdded(openGroup.server, openGroup.room)
|
OpenGroupManager.add(
|
||||||
|
server = openGroup.server,
|
||||||
|
room = openGroup.room,
|
||||||
|
publicKey = openGroup.serverPublicKey,
|
||||||
|
context = activity
|
||||||
|
)
|
||||||
|
|
||||||
|
storage.onOpenGroupAdded(openGroup.server, openGroup.room)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Toast.makeText(activity, R.string.communityErrorDescription, Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, R.string.communityErrorDescription, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -328,7 +328,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
|||||||
contentValues.put(HAS_MENTION, 0)
|
contentValues.put(HAS_MENTION, 0)
|
||||||
database.update(TABLE_NAME, contentValues, ID_WHERE, arrayOf(messageId.toString()))
|
database.update(TABLE_NAME, contentValues, ID_WHERE, arrayOf(messageId.toString()))
|
||||||
val attachmentDatabase = get(context).attachmentDatabase()
|
val attachmentDatabase = get(context).attachmentDatabase()
|
||||||
queue(Runnable { attachmentDatabase.deleteAttachmentsForMessage(messageId) })
|
queue { attachmentDatabase.deleteAttachmentsForMessage(messageId) }
|
||||||
val threadId = getThreadIdForMessage(messageId)
|
val threadId = getThreadIdForMessage(messageId)
|
||||||
|
|
||||||
markAs(messageId, MmsSmsColumns.Types.BASE_DELETED_TYPE, threadId)
|
markAs(messageId, MmsSmsColumns.Types.BASE_DELETED_TYPE, threadId)
|
||||||
@ -889,7 +889,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
|||||||
}
|
}
|
||||||
val idsAsString = queryBuilder.toString()
|
val idsAsString = queryBuilder.toString()
|
||||||
val attachmentDatabase = get(context).attachmentDatabase()
|
val attachmentDatabase = get(context).attachmentDatabase()
|
||||||
queue(Runnable { attachmentDatabase.deleteAttachmentsForMessages(messageIds) })
|
queue { attachmentDatabase.deleteAttachmentsForMessages(messageIds) }
|
||||||
val groupReceiptDatabase = get(context).groupReceiptDatabase()
|
val groupReceiptDatabase = get(context).groupReceiptDatabase()
|
||||||
groupReceiptDatabase.deleteRowsForMessages(messageIds)
|
groupReceiptDatabase.deleteRowsForMessages(messageIds)
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
@ -906,7 +906,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
|||||||
override fun deleteMessage(messageId: Long): Boolean {
|
override fun deleteMessage(messageId: Long): Boolean {
|
||||||
val threadId = getThreadIdForMessage(messageId)
|
val threadId = getThreadIdForMessage(messageId)
|
||||||
val attachmentDatabase = get(context).attachmentDatabase()
|
val attachmentDatabase = get(context).attachmentDatabase()
|
||||||
queue(Runnable { attachmentDatabase.deleteAttachmentsForMessage(messageId) })
|
queue { attachmentDatabase.deleteAttachmentsForMessage(messageId) }
|
||||||
val groupReceiptDatabase = get(context).groupReceiptDatabase()
|
val groupReceiptDatabase = get(context).groupReceiptDatabase()
|
||||||
groupReceiptDatabase.deleteRowsForMessage(messageId)
|
groupReceiptDatabase.deleteRowsForMessage(messageId)
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
@ -925,7 +925,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
|||||||
val attachmentDatabase = get(context).attachmentDatabase()
|
val attachmentDatabase = get(context).attachmentDatabase()
|
||||||
val groupReceiptDatabase = get(context).groupReceiptDatabase()
|
val groupReceiptDatabase = get(context).groupReceiptDatabase()
|
||||||
|
|
||||||
queue(Runnable { attachmentDatabase.deleteAttachmentsForMessages(messageIds) })
|
queue { attachmentDatabase.deleteAttachmentsForMessages(messageIds) }
|
||||||
groupReceiptDatabase.deleteRowsForMessages(messageIds)
|
groupReceiptDatabase.deleteRowsForMessages(messageIds)
|
||||||
|
|
||||||
val db = databaseHelper.writableDatabase
|
val db = databaseHelper.writableDatabase
|
||||||
|
@ -222,7 +222,7 @@ class ConfigFactory @Inject constructor(
|
|||||||
private val groupConfigs = ConcurrentHashMap<AccountId, GroupConfigsImpl>()
|
private val groupConfigs = ConcurrentHashMap<AccountId, GroupConfigsImpl>()
|
||||||
|
|
||||||
private val _configUpdateNotifications = MutableSharedFlow<ConfigUpdateNotification>(
|
private val _configUpdateNotifications = MutableSharedFlow<ConfigUpdateNotification>(
|
||||||
extraBufferCapacity = 1,
|
extraBufferCapacity = 5, // The notifications are normally important so we can afford to buffer a few
|
||||||
onBufferOverflow = BufferOverflow.SUSPEND
|
onBufferOverflow = BufferOverflow.SUSPEND
|
||||||
)
|
)
|
||||||
override val configUpdateNotifications get() = _configUpdateNotifications
|
override val configUpdateNotifications get() = _configUpdateNotifications
|
||||||
@ -260,15 +260,15 @@ class ConfigFactory @Inject constructor(
|
|||||||
* @param cb A function that takes a [UserConfigsImpl] and returns a pair of the result of the operation and a boolean indicating if the configs were changed.
|
* @param cb A function that takes a [UserConfigsImpl] and returns a pair of the result of the operation and a boolean indicating if the configs were changed.
|
||||||
*/
|
*/
|
||||||
private fun <T> doWithMutableUserConfigs(cb: (UserConfigsImpl) -> Pair<T, Boolean>): T {
|
private fun <T> doWithMutableUserConfigs(cb: (UserConfigsImpl) -> Pair<T, Boolean>): T {
|
||||||
return withUserConfigs { configs ->
|
val (result, changed) = withUserConfigs { configs ->
|
||||||
val (result, changed) = cb(configs as UserConfigsImpl)
|
cb(configs as UserConfigsImpl)
|
||||||
|
|
||||||
if (changed) {
|
|
||||||
_configUpdateNotifications.tryEmit(ConfigUpdateNotification.UserConfigs)
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
_configUpdateNotifications.tryEmit(ConfigUpdateNotification.UserConfigs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mergeUserConfigs(
|
override fun mergeUserConfigs(
|
||||||
@ -319,19 +319,19 @@ class ConfigFactory @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun <T> doWithMutableGroupConfigs(groupId: AccountId, cb: (GroupConfigsImpl) -> Pair<T, Boolean>): T {
|
private fun <T> doWithMutableGroupConfigs(groupId: AccountId, cb: (GroupConfigsImpl) -> Pair<T, Boolean>): T {
|
||||||
return withGroupConfigs(groupId) { configs ->
|
val (result, changed) = withGroupConfigs(groupId) { configs ->
|
||||||
val (result, changed) = cb(configs as GroupConfigsImpl)
|
cb(configs as GroupConfigsImpl)
|
||||||
|
|
||||||
Log.d("ConfigFactory", "Group updated? $groupId: $changed")
|
|
||||||
|
|
||||||
if (changed) {
|
|
||||||
if (!_configUpdateNotifications.tryEmit(ConfigUpdateNotification.GroupConfigsUpdated(groupId))) {
|
|
||||||
Log.e("ConfigFactory", "Unable to deliver group update notification")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.d("ConfigFactory", "Group updated? $groupId: $changed")
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
if (!_configUpdateNotifications.tryEmit(ConfigUpdateNotification.GroupConfigsUpdated(groupId))) {
|
||||||
|
Log.e("ConfigFactory", "Unable to deliver group update notification")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <T> withMutableGroupConfigs(
|
override fun <T> withMutableGroupConfigs(
|
||||||
|
@ -240,7 +240,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
lifecycleScope.launchWhenStarted {
|
lifecycleScope.launchWhenStarted {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.Default) {
|
||||||
// Double check that the long poller is up
|
// Double check that the long poller is up
|
||||||
(applicationContext as ApplicationContext).startPollingIfNeeded()
|
(applicationContext as ApplicationContext).startPollingIfNeeded()
|
||||||
// update things based on TextSecurePrefs (profile info etc)
|
// update things based on TextSecurePrefs (profile info etc)
|
||||||
@ -516,7 +516,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
.put(NAME_KEY, thread.recipient.name)
|
.put(NAME_KEY, thread.recipient.name)
|
||||||
.format())
|
.format())
|
||||||
dangerButton(R.string.block, R.string.AccessibilityId_blockConfirm) {
|
dangerButton(R.string.block, R.string.AccessibilityId_blockConfirm) {
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.Default) {
|
||||||
storage.setBlocked(listOf(thread.recipient), true)
|
storage.setBlocked(listOf(thread.recipient), true)
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
@ -536,7 +536,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
title(R.string.blockUnblock)
|
title(R.string.blockUnblock)
|
||||||
text(Phrase.from(context, R.string.blockUnblockName).put(NAME_KEY, thread.recipient.name).format())
|
text(Phrase.from(context, R.string.blockUnblockName).put(NAME_KEY, thread.recipient.name).format())
|
||||||
dangerButton(R.string.blockUnblock, R.string.AccessibilityId_unblockConfirm) {
|
dangerButton(R.string.blockUnblock, R.string.AccessibilityId_unblockConfirm) {
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.Default) {
|
||||||
storage.setBlocked(listOf(thread.recipient), false)
|
storage.setBlocked(listOf(thread.recipient), false)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
binding.recyclerView.adapter!!.notifyDataSetChanged()
|
binding.recyclerView.adapter!!.notifyDataSetChanged()
|
||||||
@ -549,7 +549,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
|
|
||||||
private fun setConversationMuted(thread: ThreadRecord, isMuted: Boolean) {
|
private fun setConversationMuted(thread: ThreadRecord, isMuted: Boolean) {
|
||||||
if (!isMuted) {
|
if (!isMuted) {
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.Default) {
|
||||||
recipientDatabase.setMuted(thread.recipient, 0)
|
recipientDatabase.setMuted(thread.recipient, 0)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
binding.recyclerView.adapter!!.notifyDataSetChanged()
|
binding.recyclerView.adapter!!.notifyDataSetChanged()
|
||||||
@ -557,7 +557,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showMuteDialog(this) { until ->
|
showMuteDialog(this) { until ->
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.Default) {
|
||||||
Log.d("", "**** until: $until")
|
Log.d("", "**** until: $until")
|
||||||
recipientDatabase.setMuted(thread.recipient, until)
|
recipientDatabase.setMuted(thread.recipient, until)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
@ -569,7 +569,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setNotifyType(thread: ThreadRecord, newNotifyType: Int) {
|
private fun setNotifyType(thread: ThreadRecord, newNotifyType: Int) {
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.Default) {
|
||||||
recipientDatabase.setNotifyType(thread.recipient, newNotifyType)
|
recipientDatabase.setNotifyType(thread.recipient, newNotifyType)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
binding.recyclerView.adapter!!.notifyDataSetChanged()
|
binding.recyclerView.adapter!!.notifyDataSetChanged()
|
||||||
@ -578,14 +578,14 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setConversationPinned(threadId: Long, pinned: Boolean) {
|
private fun setConversationPinned(threadId: Long, pinned: Boolean) {
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.Default) {
|
||||||
storage.setPinned(threadId, pinned)
|
storage.setPinned(threadId, pinned)
|
||||||
homeViewModel.tryReload()
|
homeViewModel.tryReload()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun markAllAsRead(thread: ThreadRecord) {
|
private fun markAllAsRead(thread: ThreadRecord) {
|
||||||
ThreadUtils.queue {
|
lifecycleScope.launch(Dispatchers.Default) {
|
||||||
MessagingModuleConfiguration.shared.storage.markConversationAsRead(thread.threadId, SnodeAPI.nowWithOffset)
|
MessagingModuleConfiguration.shared.storage.markConversationAsRead(thread.threadId, SnodeAPI.nowWithOffset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -656,7 +656,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
context
|
context
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.Default) {
|
||||||
threadDb.deleteConversation(threadID)
|
threadDb.deleteConversation(threadID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,15 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.AsyncTask
|
import android.os.AsyncTask
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.session.libsession.database.userAuth
|
import org.session.libsession.database.userAuth
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration.Companion.shared
|
import org.session.libsession.messaging.MessagingModuleConfiguration.Companion.shared
|
||||||
import org.session.libsession.messaging.messages.control.ReadReceipt
|
import org.session.libsession.messaging.messages.control.ReadReceipt
|
||||||
import org.session.libsession.messaging.sending_receiving.MessageSender.send
|
import org.session.libsession.messaging.sending_receiving.MessageSender.send
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
import org.session.libsession.snode.SnodeAPI.nowWithOffset
|
import org.session.libsession.snode.SnodeAPI.nowWithOffset
|
||||||
|
import org.session.libsession.snode.utilities.await
|
||||||
import org.session.libsession.utilities.SSKEnvironment
|
import org.session.libsession.utilities.SSKEnvironment
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.isReadReceiptsEnabled
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.isReadReceiptsEnabled
|
||||||
import org.session.libsession.utilities.associateByNotNull
|
import org.session.libsession.utilities.associateByNotNull
|
||||||
@ -72,9 +75,11 @@ class MarkReadReceiver : BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
.forEach { messageExpirationManager.startDisappearAfterRead(it.timetamp, it.address.serialize()) }
|
.forEach { messageExpirationManager.startDisappearAfterRead(it.timetamp, it.address.serialize()) }
|
||||||
|
|
||||||
hashToDisappearAfterReadMessage(context, markedReadMessages)?.let {
|
hashToDisappearAfterReadMessage(context, markedReadMessages)?.let { hashToMessages ->
|
||||||
fetchUpdatedExpiriesAndScheduleDeletion(context, it)
|
GlobalScope.launch {
|
||||||
shortenExpiryOfDisappearingAfterRead(context, it)
|
fetchUpdatedExpiriesAndScheduleDeletion(context, hashToMessages)
|
||||||
|
shortenExpiryOfDisappearingAfterRead(hashToMessages)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +96,6 @@ class MarkReadReceiver : BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun shortenExpiryOfDisappearingAfterRead(
|
private fun shortenExpiryOfDisappearingAfterRead(
|
||||||
context: Context,
|
|
||||||
hashToMessage: Map<String, MarkedMessageInfo>
|
hashToMessage: Map<String, MarkedMessageInfo>
|
||||||
) {
|
) {
|
||||||
hashToMessage.entries
|
hashToMessage.entries
|
||||||
@ -125,12 +129,12 @@ class MarkReadReceiver : BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchUpdatedExpiriesAndScheduleDeletion(
|
private suspend fun fetchUpdatedExpiriesAndScheduleDeletion(
|
||||||
context: Context,
|
context: Context,
|
||||||
hashToMessage: Map<String, MarkedMessageInfo>
|
hashToMessage: Map<String, MarkedMessageInfo>
|
||||||
) {
|
) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val expiries = SnodeAPI.getExpiries(hashToMessage.keys.toList(), shared.storage.userAuth!!).get()["expiries"] as Map<String, Long>
|
val expiries = SnodeAPI.getExpiries(hashToMessage.keys.toList(), shared.storage.userAuth!!).await()["expiries"] as Map<String, Long>
|
||||||
hashToMessage.forEach { (hash, info) -> expiries[hash]?.let { scheduleDeletion(context, info.expirationInfo, it - info.expirationInfo.expireStarted) } }
|
hashToMessage.forEach { (hash, info) -> expiries[hash]?.let { scheduleDeletion(context, info.expirationInfo, it - info.expirationInfo.expireStarted) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@ import org.thoughtcrime.securesms.groups.OpenGroupManager;
|
|||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import kotlin.Unit;
|
||||||
|
|
||||||
public class OptimizedMessageNotifier implements MessageNotifier {
|
public class OptimizedMessageNotifier implements MessageNotifier {
|
||||||
private final MessageNotifier wrapped;
|
private final MessageNotifier wrapped;
|
||||||
private final Debouncer debouncer;
|
private final Debouncer debouncer;
|
||||||
@ -118,7 +120,10 @@ public class OptimizedMessageNotifier implements MessageNotifier {
|
|||||||
|
|
||||||
private void performOnBackgroundThreadIfNeeded(Runnable r) {
|
private void performOnBackgroundThreadIfNeeded(Runnable r) {
|
||||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||||
ThreadUtils.queue(r);
|
ThreadUtils.queue(() -> {
|
||||||
|
r.run();
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
r.run();
|
r.run();
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import android.content.Intent
|
|||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import com.opencsv.CSVReader
|
import com.opencsv.CSVReader
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.session.libsession.snode.OnionRequestAPI
|
import org.session.libsession.snode.OnionRequestAPI
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.session.libsignal.utilities.ThreadUtils
|
import org.session.libsignal.utilities.ThreadUtils
|
||||||
@ -133,7 +135,7 @@ class IP2Country private constructor(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun populateCacheIfNeeded() {
|
private fun populateCacheIfNeeded() {
|
||||||
ThreadUtils.queue {
|
GlobalScope.launch {
|
||||||
OnionRequestAPI.paths.iterator().forEach { path ->
|
OnionRequestAPI.paths.iterator().forEach { path ->
|
||||||
path.iterator().forEach { snode ->
|
path.iterator().forEach { snode ->
|
||||||
cacheCountryForIP(snode.ip) // Preload if needed
|
cacheCountryForIP(snode.ip) // Preload if needed
|
||||||
|
@ -53,7 +53,6 @@ inline jobject serialize_closed_group(JNIEnv* env, session::config::convo::group
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline jobject serialize_any(JNIEnv *env, session::config::convo::any any) {
|
inline jobject serialize_any(JNIEnv *env, session::config::convo::any any) {
|
||||||
__android_log_print(ANDROID_LOG_WARN, "DESERIALIE", "deserializing any");
|
|
||||||
if (auto* dm = std::get_if<session::config::convo::one_to_one>(&any)) {
|
if (auto* dm = std::get_if<session::config::convo::one_to_one>(&any)) {
|
||||||
return serialize_one_to_one(env, *dm);
|
return serialize_one_to_one(env, *dm);
|
||||||
} else if (auto* og = std::get_if<session::config::convo::community>(&any)) {
|
} else if (auto* og = std::get_if<session::config::convo::community>(&any)) {
|
||||||
|
@ -6,10 +6,14 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.asCoroutineDispatcher
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import network.loki.messenger.libsession_util.util.ConfigPush
|
import network.loki.messenger.libsession_util.util.ConfigPush
|
||||||
import org.session.libsession.database.StorageProtocol
|
import org.session.libsession.database.StorageProtocol
|
||||||
import org.session.libsession.database.userAuth
|
import org.session.libsession.database.userAuth
|
||||||
@ -29,6 +33,7 @@ import org.session.libsignal.utilities.Base64
|
|||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.session.libsignal.utilities.Namespace
|
import org.session.libsignal.utilities.Namespace
|
||||||
import org.session.libsignal.utilities.Snode
|
import org.session.libsignal.utilities.Snode
|
||||||
|
import java.util.concurrent.Executors
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val TAG = "ConfigSyncHandler"
|
private const val TAG = "ConfigSyncHandler"
|
||||||
@ -44,32 +49,35 @@ class ConfigSyncHandler @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
private var job: Job? = null
|
private var job: Job? = null
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class, DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
fun start() {
|
fun start() {
|
||||||
require(job == null) { "Already started" }
|
require(job == null) { "Already started" }
|
||||||
|
|
||||||
job = GlobalScope.launch {
|
job = GlobalScope.launch {
|
||||||
val groupDispatchers = hashMapOf<AccountId, CoroutineDispatcher>()
|
val groupMutex = hashMapOf<AccountId, Mutex>()
|
||||||
val userConfigDispatcher = Dispatchers.Default.limitedParallelism(1)
|
val userMutex = Mutex()
|
||||||
|
|
||||||
configFactory.configUpdateNotifications.collect { changes ->
|
configFactory.configUpdateNotifications
|
||||||
|
.collect { changes ->
|
||||||
try {
|
try {
|
||||||
when (changes) {
|
when (changes) {
|
||||||
is ConfigUpdateNotification.GroupConfigsDeleted -> {
|
is ConfigUpdateNotification.GroupConfigsDeleted -> {
|
||||||
groupDispatchers.remove(changes.groupId)
|
groupMutex.remove(changes.groupId)
|
||||||
}
|
}
|
||||||
|
|
||||||
is ConfigUpdateNotification.GroupConfigsUpdated -> {
|
is ConfigUpdateNotification.GroupConfigsUpdated -> {
|
||||||
// Group config pushing is limited to its own dispatcher
|
// Group config pushing is limited to its own dispatcher
|
||||||
launch(groupDispatchers.getOrPut(changes.groupId) {
|
launch {
|
||||||
Dispatchers.Default.limitedParallelism(1)
|
groupMutex.getOrPut(changes.groupId) { Mutex() }.withLock {
|
||||||
}) {
|
pushGroupConfigsChangesIfNeeded(changes.groupId)
|
||||||
pushGroupConfigsChangesIfNeeded(changes.groupId)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigUpdateNotification.UserConfigs -> launch(userConfigDispatcher) {
|
ConfigUpdateNotification.UserConfigs -> launch {
|
||||||
pushUserConfigChangesIfNeeded()
|
userMutex.withLock {
|
||||||
|
pushUserConfigChangesIfNeeded()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -12,6 +12,7 @@ import org.session.libsession.messaging.messages.Message
|
|||||||
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
||||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||||
import org.session.libsession.messaging.utilities.Data
|
import org.session.libsession.messaging.utilities.Data
|
||||||
|
import org.session.libsession.snode.utilities.await
|
||||||
import org.session.libsession.utilities.DecodedAudio
|
import org.session.libsession.utilities.DecodedAudio
|
||||||
import org.session.libsession.utilities.InputStreamMediaDataSource
|
import org.session.libsession.utilities.InputStreamMediaDataSource
|
||||||
import org.session.libsession.utilities.UploadResult
|
import org.session.libsession.utilities.UploadResult
|
||||||
@ -76,7 +77,7 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun upload(attachment: SignalServiceAttachmentStream, server: String, encrypt: Boolean, upload: (ByteArray) -> Promise<Long, Exception>): Pair<ByteArray, UploadResult> {
|
private suspend fun upload(attachment: SignalServiceAttachmentStream, server: String, encrypt: Boolean, upload: (ByteArray) -> Promise<Long, Exception>): Pair<ByteArray, UploadResult> {
|
||||||
// Key
|
// Key
|
||||||
val key = if (encrypt) Util.getSecretBytes(64) else ByteArray(0)
|
val key = if (encrypt) Util.getSecretBytes(64) else ByteArray(0)
|
||||||
// Length
|
// Length
|
||||||
@ -102,7 +103,7 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
|
|||||||
drb.writeTo(b)
|
drb.writeTo(b)
|
||||||
val data = b.readByteArray()
|
val data = b.readByteArray()
|
||||||
// Upload the data
|
// Upload the data
|
||||||
val id = upload(data).get()
|
val id = upload(data).await()
|
||||||
val digest = drb.transmittedDigest
|
val digest = drb.transmittedDigest
|
||||||
// Return
|
// Return
|
||||||
return Pair(key, UploadResult(id, "${server}/file/$id", digest))
|
return Pair(key, UploadResult(id, "${server}/file/$id", digest))
|
||||||
|
@ -24,7 +24,7 @@ class JobQueue : JobDelegate {
|
|||||||
private var hasResumedPendingJobs = false // Just for debugging
|
private var hasResumedPendingJobs = false // Just for debugging
|
||||||
private val jobTimestampMap = ConcurrentHashMap<Long, AtomicInteger>()
|
private val jobTimestampMap = ConcurrentHashMap<Long, AtomicInteger>()
|
||||||
|
|
||||||
private val scope = GlobalScope
|
private val scope: CoroutineScope = GlobalScope
|
||||||
private val queue = Channel<Job>(UNLIMITED)
|
private val queue = Channel<Job>(UNLIMITED)
|
||||||
private val pendingJobIds = mutableSetOf<String>()
|
private val pendingJobIds = mutableSetOf<String>()
|
||||||
|
|
||||||
@ -34,9 +34,8 @@ class JobQueue : JobDelegate {
|
|||||||
|
|
||||||
private fun CoroutineScope.processWithOpenGroupDispatcher(
|
private fun CoroutineScope.processWithOpenGroupDispatcher(
|
||||||
channel: Channel<Job>,
|
channel: Channel<Job>,
|
||||||
dispatcher: CoroutineDispatcher,
|
|
||||||
name: String
|
name: String
|
||||||
) = launch(dispatcher) {
|
) = launch {
|
||||||
for (job in channel) {
|
for (job in channel) {
|
||||||
if (!isActive) break
|
if (!isActive) break
|
||||||
val openGroupId = when (job) {
|
val openGroupId = when (job) {
|
||||||
@ -54,7 +53,7 @@ class JobQueue : JobDelegate {
|
|||||||
val groupChannel = if (!openGroupChannels.containsKey(openGroupId)) {
|
val groupChannel = if (!openGroupChannels.containsKey(openGroupId)) {
|
||||||
Log.d("OpenGroupDispatcher", "Creating ${openGroupId.hashCode()} channel")
|
Log.d("OpenGroupDispatcher", "Creating ${openGroupId.hashCode()} channel")
|
||||||
val newGroupChannel = Channel<Job>(UNLIMITED)
|
val newGroupChannel = Channel<Job>(UNLIMITED)
|
||||||
launch(dispatcher) {
|
launch {
|
||||||
for (groupJob in newGroupChannel) {
|
for (groupJob in newGroupChannel) {
|
||||||
if (!isActive) break
|
if (!isActive) break
|
||||||
groupJob.process(name)
|
groupJob.process(name)
|
||||||
@ -74,14 +73,13 @@ class JobQueue : JobDelegate {
|
|||||||
|
|
||||||
private fun CoroutineScope.processWithDispatcher(
|
private fun CoroutineScope.processWithDispatcher(
|
||||||
channel: Channel<Job>,
|
channel: Channel<Job>,
|
||||||
dispatcher: CoroutineDispatcher,
|
|
||||||
name: String,
|
name: String,
|
||||||
asynchronous: Boolean = true
|
asynchronous: Boolean = true
|
||||||
) = launch(dispatcher) {
|
) = launch {
|
||||||
for (job in channel) {
|
for (job in channel) {
|
||||||
if (!isActive) break
|
if (!isActive) break
|
||||||
if (asynchronous) {
|
if (asynchronous) {
|
||||||
launch(dispatcher) {
|
launch {
|
||||||
job.process(name)
|
job.process(name)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -111,10 +109,10 @@ class JobQueue : JobDelegate {
|
|||||||
val mediaQueue = Channel<Job>(capacity = UNLIMITED)
|
val mediaQueue = Channel<Job>(capacity = UNLIMITED)
|
||||||
val openGroupQueue = Channel<Job>(capacity = UNLIMITED)
|
val openGroupQueue = Channel<Job>(capacity = UNLIMITED)
|
||||||
|
|
||||||
val receiveJob = processWithDispatcher(rxQueue, Dispatchers.Default.limitedParallelism(1), "rx", asynchronous = false)
|
val receiveJob = processWithDispatcher(rxQueue, "rx", asynchronous = false)
|
||||||
val txJob = processWithDispatcher(txQueue, Dispatchers.Default.limitedParallelism(1), "tx")
|
val txJob = processWithDispatcher(txQueue, "tx")
|
||||||
val mediaJob = processWithDispatcher(mediaQueue, Dispatchers.Default.limitedParallelism(4), "media")
|
val mediaJob = processWithDispatcher(mediaQueue, "media")
|
||||||
val openGroupJob = processWithOpenGroupDispatcher(openGroupQueue, Dispatchers.Default.limitedParallelism(8), "openGroup")
|
val openGroupJob = processWithOpenGroupDispatcher(openGroupQueue, "openGroup")
|
||||||
|
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
when (val job = queue.receive()) {
|
when (val job = queue.receive()) {
|
||||||
|
@ -7,6 +7,7 @@ import org.session.libsession.messaging.messages.Message
|
|||||||
import org.session.libsession.messaging.sending_receiving.MessageReceiver
|
import org.session.libsession.messaging.sending_receiving.MessageReceiver
|
||||||
import org.session.libsession.messaging.sending_receiving.handle
|
import org.session.libsession.messaging.sending_receiving.handle
|
||||||
import org.session.libsession.messaging.utilities.Data
|
import org.session.libsession.messaging.utilities.Data
|
||||||
|
import org.session.libsession.snode.utilities.await
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
|
|
||||||
class MessageReceiveJob(val data: ByteArray, val serverHash: String? = null, val openGroupMessageServerID: Long? = null, val openGroupID: String? = null) : Job {
|
class MessageReceiveJob(val data: ByteArray, val serverHash: String? = null, val openGroupMessageServerID: Long? = null, val openGroupID: String? = null) : Job {
|
||||||
@ -27,7 +28,7 @@ class MessageReceiveJob(val data: ByteArray, val serverHash: String? = null, val
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun execute(dispatcherName: String) {
|
override suspend fun execute(dispatcherName: String) {
|
||||||
executeAsync(dispatcherName).get()
|
executeAsync(dispatcherName).await()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun executeAsync(dispatcherName: String): Promise<Unit, Exception> {
|
fun executeAsync(dispatcherName: String): Promise<Unit, Exception> {
|
||||||
|
@ -8,7 +8,9 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming
|
|||||||
import com.fasterxml.jackson.databind.type.TypeFactory
|
import com.fasterxml.jackson.databind.type.TypeFactory
|
||||||
import com.goterl.lazysodium.interfaces.GenericHash
|
import com.goterl.lazysodium.interfaces.GenericHash
|
||||||
import com.goterl.lazysodium.interfaces.Sign
|
import com.goterl.lazysodium.interfaces.Sign
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.functional.map
|
import nl.komponents.kovenant.functional.map
|
||||||
import okhttp3.Headers.Companion.toHeaders
|
import okhttp3.Headers.Companion.toHeaders
|
||||||
@ -22,6 +24,8 @@ import org.session.libsession.messaging.utilities.SodiumUtilities.sodium
|
|||||||
import org.session.libsession.snode.OnionRequestAPI
|
import org.session.libsession.snode.OnionRequestAPI
|
||||||
import org.session.libsession.snode.OnionResponse
|
import org.session.libsession.snode.OnionResponse
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
|
import org.session.libsession.snode.utilities.asyncPromise
|
||||||
|
import org.session.libsession.snode.utilities.await
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsignal.utilities.AccountId
|
import org.session.libsignal.utilities.AccountId
|
||||||
import org.session.libsignal.utilities.Base64.decode
|
import org.session.libsignal.utilities.Base64.decode
|
||||||
@ -858,7 +862,9 @@ object OpenGroupApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getDefaultRoomsIfNeeded(): Promise<List<DefaultGroup>, Exception> {
|
fun getDefaultRoomsIfNeeded(): Promise<List<DefaultGroup>, Exception> {
|
||||||
return getAllRooms().map { groups ->
|
return GlobalScope.asyncPromise {
|
||||||
|
val groups = getAllRooms().await()
|
||||||
|
|
||||||
val earlyGroups = groups.map { group ->
|
val earlyGroups = groups.map { group ->
|
||||||
DefaultGroup(group.token, group.name, null)
|
DefaultGroup(group.token, group.name, null)
|
||||||
}
|
}
|
||||||
@ -873,15 +879,13 @@ object OpenGroupApi {
|
|||||||
}
|
}
|
||||||
groups.map { group ->
|
groups.map { group ->
|
||||||
val image = try {
|
val image = try {
|
||||||
images[group.token]!!.get()
|
images[group.token]!!.await()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// No image or image failed to download
|
// No image or image failed to download
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
DefaultGroup(group.token, group.name, image)
|
DefaultGroup(group.token, group.name, image)
|
||||||
}
|
}.also(defaultRooms::tryEmit)
|
||||||
}.success { new ->
|
|
||||||
defaultRooms.tryEmit(new)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
package org.session.libsession.messaging.sending_receiving
|
package org.session.libsession.messaging.sending_receiving
|
||||||
|
|
||||||
import com.google.protobuf.ByteString
|
import com.google.protobuf.ByteString
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.deferred
|
import nl.komponents.kovenant.deferred
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
@ -14,6 +15,8 @@ import org.session.libsession.messaging.sending_receiving.MessageSender.Error
|
|||||||
import org.session.libsession.messaging.sending_receiving.notifications.PushRegistryV1
|
import org.session.libsession.messaging.sending_receiving.notifications.PushRegistryV1
|
||||||
import org.session.libsession.messaging.sending_receiving.pollers.LegacyClosedGroupPollerV2
|
import org.session.libsession.messaging.sending_receiving.pollers.LegacyClosedGroupPollerV2
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
|
import org.session.libsession.snode.utilities.asyncPromise
|
||||||
|
import org.session.libsession.snode.utilities.await
|
||||||
import org.session.libsession.utilities.Address
|
import org.session.libsession.utilities.Address
|
||||||
import org.session.libsession.utilities.Address.Companion.fromSerialized
|
import org.session.libsession.utilities.Address.Companion.fromSerialized
|
||||||
import org.session.libsession.utilities.Device
|
import org.session.libsession.utilities.Device
|
||||||
@ -41,10 +44,8 @@ fun MessageSender.create(
|
|||||||
name: String,
|
name: String,
|
||||||
members: Collection<String>
|
members: Collection<String>
|
||||||
): Promise<String, Exception> {
|
): Promise<String, Exception> {
|
||||||
val deferred = deferred<String, Exception>()
|
return GlobalScope.asyncPromise {
|
||||||
ThreadUtils.queue {
|
|
||||||
// Prepare
|
// Prepare
|
||||||
val context = MessagingModuleConfiguration.shared.context
|
|
||||||
val storage = MessagingModuleConfiguration.shared.storage
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
val userPublicKey = storage.getUserPublicKey()!!
|
val userPublicKey = storage.getUserPublicKey()!!
|
||||||
val membersAsData = members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }
|
val membersAsData = members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }
|
||||||
@ -83,7 +84,7 @@ fun MessageSender.create(
|
|||||||
val closedGroupControlMessage = ClosedGroupControlMessage(closedGroupUpdateKind, groupID)
|
val closedGroupControlMessage = ClosedGroupControlMessage(closedGroupUpdateKind, groupID)
|
||||||
closedGroupControlMessage.sentTimestamp = sentTime
|
closedGroupControlMessage.sentTimestamp = sentTime
|
||||||
try {
|
try {
|
||||||
sendNonDurably(closedGroupControlMessage, Address.fromSerialized(member), member == ourPubKey).get()
|
sendNonDurably(closedGroupControlMessage, Address.fromSerialized(member), member == ourPubKey).await()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// We failed to properly create the group so delete it's associated data (in the past
|
// We failed to properly create the group so delete it's associated data (in the past
|
||||||
// we didn't create this data until the messages successfully sent but this resulted
|
// we didn't create this data until the messages successfully sent but this resulted
|
||||||
@ -91,8 +92,7 @@ fun MessageSender.create(
|
|||||||
storage.removeClosedGroupPublicKey(groupPublicKey)
|
storage.removeClosedGroupPublicKey(groupPublicKey)
|
||||||
storage.removeAllClosedGroupEncryptionKeyPairs(groupPublicKey)
|
storage.removeAllClosedGroupEncryptionKeyPairs(groupPublicKey)
|
||||||
storage.deleteConversation(threadID)
|
storage.deleteConversation(threadID)
|
||||||
deferred.reject(e)
|
throw e
|
||||||
return@queue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,11 +102,8 @@ fun MessageSender.create(
|
|||||||
PushRegistryV1.register(device = device, publicKey = userPublicKey)
|
PushRegistryV1.register(device = device, publicKey = userPublicKey)
|
||||||
// Start polling
|
// Start polling
|
||||||
LegacyClosedGroupPollerV2.shared.startPolling(groupPublicKey)
|
LegacyClosedGroupPollerV2.shared.startPolling(groupPublicKey)
|
||||||
// Fulfill the promise
|
groupID
|
||||||
deferred.resolve(groupID)
|
|
||||||
}
|
}
|
||||||
// Return
|
|
||||||
return deferred.promise
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MessageSender.setName(groupPublicKey: String, newName: String) {
|
fun MessageSender.setName(groupPublicKey: String, newName: String) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.session.libsession.messaging.sending_receiving.pollers
|
package org.session.libsession.messaging.sending_receiving.pollers
|
||||||
|
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.functional.bind
|
import nl.komponents.kovenant.functional.bind
|
||||||
import nl.komponents.kovenant.functional.map
|
import nl.komponents.kovenant.functional.map
|
||||||
@ -9,6 +10,8 @@ import org.session.libsession.messaging.jobs.BatchMessageReceiveJob
|
|||||||
import org.session.libsession.messaging.jobs.JobQueue
|
import org.session.libsession.messaging.jobs.JobQueue
|
||||||
import org.session.libsession.messaging.jobs.MessageReceiveParameters
|
import org.session.libsession.messaging.jobs.MessageReceiveParameters
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
|
import org.session.libsession.snode.utilities.asyncPromise
|
||||||
|
import org.session.libsession.snode.utilities.await
|
||||||
import org.session.libsession.utilities.GroupUtil
|
import org.session.libsession.utilities.GroupUtil
|
||||||
import org.session.libsignal.crypto.secureRandomOrNull
|
import org.session.libsignal.crypto.secureRandomOrNull
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
@ -110,13 +113,13 @@ class LegacyClosedGroupPollerV2 {
|
|||||||
when {
|
when {
|
||||||
currentForkInfo.defaultRequiresAuth() -> SnodeAPI.getUnauthenticatedRawMessages(snode, groupPublicKey, namespace = Namespace.UNAUTHENTICATED_CLOSED_GROUP())
|
currentForkInfo.defaultRequiresAuth() -> SnodeAPI.getUnauthenticatedRawMessages(snode, groupPublicKey, namespace = Namespace.UNAUTHENTICATED_CLOSED_GROUP())
|
||||||
.map { SnodeAPI.parseRawMessagesResponse(it, snode, groupPublicKey, Namespace.UNAUTHENTICATED_CLOSED_GROUP()) }
|
.map { SnodeAPI.parseRawMessagesResponse(it, snode, groupPublicKey, Namespace.UNAUTHENTICATED_CLOSED_GROUP()) }
|
||||||
currentForkInfo.hasNamespaces() -> task {
|
currentForkInfo.hasNamespaces() -> GlobalScope.asyncPromise {
|
||||||
val unAuthed = SnodeAPI.getUnauthenticatedRawMessages(snode, groupPublicKey, namespace = Namespace.UNAUTHENTICATED_CLOSED_GROUP())
|
val unAuthed = SnodeAPI.getUnauthenticatedRawMessages(snode, groupPublicKey, namespace = Namespace.UNAUTHENTICATED_CLOSED_GROUP())
|
||||||
.map { SnodeAPI.parseRawMessagesResponse(it, snode, groupPublicKey, Namespace.UNAUTHENTICATED_CLOSED_GROUP()) }
|
.map { SnodeAPI.parseRawMessagesResponse(it, snode, groupPublicKey, Namespace.UNAUTHENTICATED_CLOSED_GROUP()) }
|
||||||
val default = SnodeAPI.getUnauthenticatedRawMessages(snode, groupPublicKey, namespace = Namespace.DEFAULT())
|
val default = SnodeAPI.getUnauthenticatedRawMessages(snode, groupPublicKey, namespace = Namespace.DEFAULT())
|
||||||
.map { SnodeAPI.parseRawMessagesResponse(it, snode, groupPublicKey, Namespace.DEFAULT()) }
|
.map { SnodeAPI.parseRawMessagesResponse(it, snode, groupPublicKey, Namespace.DEFAULT()) }
|
||||||
val unAuthedResult = unAuthed.get()
|
val unAuthedResult = unAuthed.await()
|
||||||
val defaultResult = default.get()
|
val defaultResult = default.await()
|
||||||
val format = DateFormat.getTimeInstance()
|
val format = DateFormat.getTimeInstance()
|
||||||
if (unAuthedResult.isNotEmpty() || defaultResult.isNotEmpty()) {
|
if (unAuthedResult.isNotEmpty() || defaultResult.isNotEmpty()) {
|
||||||
Log.d("Poller", "@${format.format(Date())}Polled ${unAuthedResult.size} from -10, ${defaultResult.size} from 0")
|
Log.d("Poller", "@${format.format(Date())}Polled ${unAuthedResult.size} from -10, ${defaultResult.size} from 0")
|
||||||
|
@ -25,12 +25,12 @@ import org.session.libsession.messaging.sending_receiving.MessageReceiver
|
|||||||
import org.session.libsession.messaging.sending_receiving.handle
|
import org.session.libsession.messaging.sending_receiving.handle
|
||||||
import org.session.libsession.messaging.sending_receiving.handleOpenGroupReactions
|
import org.session.libsession.messaging.sending_receiving.handleOpenGroupReactions
|
||||||
import org.session.libsession.snode.OnionRequestAPI
|
import org.session.libsession.snode.OnionRequestAPI
|
||||||
|
import org.session.libsession.snode.utilities.successBackground
|
||||||
import org.session.libsession.utilities.Address
|
import org.session.libsession.utilities.Address
|
||||||
import org.session.libsession.utilities.GroupUtil
|
import org.session.libsession.utilities.GroupUtil
|
||||||
import org.session.libsignal.protos.SignalServiceProtos
|
import org.session.libsignal.protos.SignalServiceProtos
|
||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.Base64
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.session.libsignal.utilities.successBackground
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.ScheduledExecutorService
|
import java.util.concurrent.ScheduledExecutorService
|
||||||
import java.util.concurrent.ScheduledFuture
|
import java.util.concurrent.ScheduledFuture
|
||||||
|
@ -2,14 +2,12 @@ package org.session.libsession.messaging.sending_receiving.pollers
|
|||||||
|
|
||||||
import android.util.SparseArray
|
import android.util.SparseArray
|
||||||
import androidx.core.util.valueIterator
|
import androidx.core.util.valueIterator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import nl.komponents.kovenant.Deferred
|
import nl.komponents.kovenant.Deferred
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.deferred
|
import nl.komponents.kovenant.deferred
|
||||||
import nl.komponents.kovenant.functional.bind
|
import nl.komponents.kovenant.functional.bind
|
||||||
import nl.komponents.kovenant.resolve
|
import nl.komponents.kovenant.resolve
|
||||||
import nl.komponents.kovenant.task
|
|
||||||
import org.session.libsession.database.StorageProtocol
|
import org.session.libsession.database.StorageProtocol
|
||||||
import org.session.libsession.database.userAuth
|
import org.session.libsession.database.userAuth
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
@ -19,6 +17,7 @@ import org.session.libsession.messaging.jobs.MessageReceiveParameters
|
|||||||
import org.session.libsession.snode.RawResponse
|
import org.session.libsession.snode.RawResponse
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
import org.session.libsession.snode.SnodeModule
|
import org.session.libsession.snode.SnodeModule
|
||||||
|
import org.session.libsession.snode.utilities.asyncPromise
|
||||||
import org.session.libsession.utilities.ConfigFactoryProtocol
|
import org.session.libsession.utilities.ConfigFactoryProtocol
|
||||||
import org.session.libsession.utilities.ConfigMessage
|
import org.session.libsession.utilities.ConfigMessage
|
||||||
import org.session.libsession.utilities.UserConfigType
|
import org.session.libsession.utilities.UserConfigType
|
||||||
@ -177,7 +176,7 @@ class Poller(
|
|||||||
return poll(snode, deferred)
|
return poll(snode, deferred)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pollUserProfile(snode: Snode, deferred: Deferred<Unit, Exception>): Promise<Unit, Exception> = task {
|
private fun pollUserProfile(snode: Snode, deferred: Deferred<Unit, Exception>): Promise<Unit, Exception> = GlobalScope.asyncPromise {
|
||||||
val requests = mutableListOf<SnodeAPI.SnodeBatchRequestInfo>()
|
val requests = mutableListOf<SnodeAPI.SnodeBatchRequestInfo>()
|
||||||
val hashesToExtend = mutableSetOf<String>()
|
val hashesToExtend = mutableSetOf<String>()
|
||||||
val userAuth = requireNotNull(MessagingModuleConfiguration.shared.storage.userAuth)
|
val userAuth = requireNotNull(MessagingModuleConfiguration.shared.storage.userAuth)
|
||||||
@ -236,116 +235,114 @@ class Poller(
|
|||||||
|
|
||||||
private fun poll(snode: Snode, deferred: Deferred<Unit, Exception>): Promise<Unit, Exception> {
|
private fun poll(snode: Snode, deferred: Deferred<Unit, Exception>): Promise<Unit, Exception> {
|
||||||
if (!hasStarted) { return Promise.ofFail(PromiseCanceledException()) }
|
if (!hasStarted) { return Promise.ofFail(PromiseCanceledException()) }
|
||||||
return task {
|
return GlobalScope.asyncPromise {
|
||||||
runBlocking(Dispatchers.IO) {
|
val userAuth = requireNotNull(MessagingModuleConfiguration.shared.storage.userAuth)
|
||||||
val userAuth = requireNotNull(MessagingModuleConfiguration.shared.storage.userAuth)
|
val requestSparseArray = SparseArray<SnodeAPI.SnodeBatchRequestInfo>()
|
||||||
val requestSparseArray = SparseArray<SnodeAPI.SnodeBatchRequestInfo>()
|
// get messages
|
||||||
// get messages
|
SnodeAPI.buildAuthenticatedRetrieveBatchRequest(
|
||||||
SnodeAPI.buildAuthenticatedRetrieveBatchRequest(
|
lastHash = lokiApiDatabase.getLastMessageHashValue(
|
||||||
lastHash = lokiApiDatabase.getLastMessageHashValue(
|
snode = snode,
|
||||||
snode = snode,
|
publicKey = userAuth.accountId.hexString,
|
||||||
publicKey = userAuth.accountId.hexString,
|
namespace = Namespace.DEFAULT()
|
||||||
namespace = Namespace.DEFAULT()
|
),
|
||||||
),
|
auth = userAuth,
|
||||||
auth = userAuth,
|
maxSize = -2)
|
||||||
maxSize = -2)
|
.also { personalMessages ->
|
||||||
.also { personalMessages ->
|
// namespaces here should always be set
|
||||||
// namespaces here should always be set
|
requestSparseArray[personalMessages.namespace!!] = personalMessages
|
||||||
requestSparseArray[personalMessages.namespace!!] = personalMessages
|
}
|
||||||
}
|
// get the latest convo info volatile
|
||||||
// get the latest convo info volatile
|
val hashesToExtend = mutableSetOf<String>()
|
||||||
val hashesToExtend = mutableSetOf<String>()
|
configFactory.withUserConfigs { configs ->
|
||||||
configFactory.withUserConfigs { configs ->
|
UserConfigType
|
||||||
UserConfigType
|
.entries
|
||||||
.entries
|
.map { type ->
|
||||||
.map { type ->
|
val config = configs.getConfig(type)
|
||||||
val config = configs.getConfig(type)
|
hashesToExtend += config.currentHashes()
|
||||||
hashesToExtend += config.currentHashes()
|
type.namespace to SnodeAPI.buildAuthenticatedRetrieveBatchRequest(
|
||||||
type.namespace to SnodeAPI.buildAuthenticatedRetrieveBatchRequest(
|
lastHash = lokiApiDatabase.getLastMessageHashValue(
|
||||||
lastHash = lokiApiDatabase.getLastMessageHashValue(
|
snode = snode,
|
||||||
snode = snode,
|
publicKey = userAuth.accountId.hexString,
|
||||||
publicKey = userAuth.accountId.hexString,
|
namespace = type.namespace
|
||||||
namespace = type.namespace
|
),
|
||||||
),
|
auth = userAuth,
|
||||||
auth = userAuth,
|
namespace = type.namespace,
|
||||||
namespace = type.namespace,
|
maxSize = -8
|
||||||
maxSize = -8
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}.forEach { (namespace, request) ->
|
|
||||||
// namespaces here should always be set
|
|
||||||
requestSparseArray[namespace] = request
|
|
||||||
}
|
|
||||||
|
|
||||||
val requests =
|
|
||||||
requestSparseArray.valueIterator().asSequence().toMutableList()
|
|
||||||
|
|
||||||
if (hashesToExtend.isNotEmpty()) {
|
|
||||||
SnodeAPI.buildAuthenticatedAlterTtlBatchRequest(
|
|
||||||
messageHashes = hashesToExtend.toList(),
|
|
||||||
auth = userAuth,
|
|
||||||
newExpiry = SnodeAPI.nowWithOffset + 14.days.inWholeMilliseconds,
|
|
||||||
extend = true
|
|
||||||
).let { extensionRequest ->
|
|
||||||
requests += extensionRequest
|
|
||||||
}
|
}
|
||||||
|
}.forEach { (namespace, request) ->
|
||||||
|
// namespaces here should always be set
|
||||||
|
requestSparseArray[namespace] = request
|
||||||
|
}
|
||||||
|
|
||||||
|
val requests =
|
||||||
|
requestSparseArray.valueIterator().asSequence().toMutableList()
|
||||||
|
|
||||||
|
if (hashesToExtend.isNotEmpty()) {
|
||||||
|
SnodeAPI.buildAuthenticatedAlterTtlBatchRequest(
|
||||||
|
messageHashes = hashesToExtend.toList(),
|
||||||
|
auth = userAuth,
|
||||||
|
newExpiry = SnodeAPI.nowWithOffset + 14.days.inWholeMilliseconds,
|
||||||
|
extend = true
|
||||||
|
).let { extensionRequest ->
|
||||||
|
requests += extensionRequest
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (requests.isNotEmpty()) {
|
if (requests.isNotEmpty()) {
|
||||||
SnodeAPI.getRawBatchResponse(snode, userPublicKey, requests).bind { rawResponses ->
|
SnodeAPI.getRawBatchResponse(snode, userPublicKey, requests).bind { rawResponses ->
|
||||||
isCaughtUp = true
|
isCaughtUp = true
|
||||||
if (deferred.promise.isDone()) {
|
if (deferred.promise.isDone()) {
|
||||||
return@bind Promise.ofSuccess(Unit)
|
return@bind Promise.ofSuccess(Unit)
|
||||||
} else {
|
} else {
|
||||||
val responseList = (rawResponses["results"] as List<RawResponse>)
|
val responseList = (rawResponses["results"] as List<RawResponse>)
|
||||||
// in case we had null configs, the array won't be fully populated
|
// in case we had null configs, the array won't be fully populated
|
||||||
// index of the sparse array key iterator should be the request index, with the key being the namespace
|
// index of the sparse array key iterator should be the request index, with the key being the namespace
|
||||||
UserConfigType.entries
|
UserConfigType.entries
|
||||||
.map { type -> type to requestSparseArray.indexOfKey(type.namespace) }
|
.map { type -> type to requestSparseArray.indexOfKey(type.namespace) }
|
||||||
.filter { (_, i) -> i >= 0 }
|
.filter { (_, i) -> i >= 0 }
|
||||||
.forEach { (configType, requestIndex) ->
|
.forEach { (configType, requestIndex) ->
|
||||||
responseList.getOrNull(requestIndex)?.let { rawResponse ->
|
responseList.getOrNull(requestIndex)?.let { rawResponse ->
|
||||||
if (rawResponse["code"] as? Int != 200) {
|
|
||||||
Log.e(TAG, "Batch sub-request had non-200 response code, returned code ${(rawResponse["code"] as? Int) ?: "[unknown]"}")
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
val body = rawResponse["body"] as? RawResponse
|
|
||||||
if (body == null) {
|
|
||||||
Log.e(TAG, "Batch sub-request didn't contain a body")
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
|
|
||||||
processConfig(snode, body, configType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the first response will be the personal messages (we want these to be processed after config messages)
|
|
||||||
val personalResponseIndex = requestSparseArray.indexOfKey(Namespace.DEFAULT())
|
|
||||||
if (personalResponseIndex >= 0) {
|
|
||||||
responseList.getOrNull(personalResponseIndex)?.let { rawResponse ->
|
|
||||||
if (rawResponse["code"] as? Int != 200) {
|
if (rawResponse["code"] as? Int != 200) {
|
||||||
Log.e(TAG, "Batch sub-request for personal messages had non-200 response code, returned code ${(rawResponse["code"] as? Int) ?: "[unknown]"}")
|
Log.e(TAG, "Batch sub-request had non-200 response code, returned code ${(rawResponse["code"] as? Int) ?: "[unknown]"}")
|
||||||
// If we got a non-success response then the snode might be bad so we should try rotate
|
return@forEach
|
||||||
// to a different one just in case
|
|
||||||
pollNextSnode(deferred = deferred)
|
|
||||||
return@bind Promise.ofSuccess(Unit)
|
|
||||||
} else {
|
|
||||||
val body = rawResponse["body"] as? RawResponse
|
|
||||||
if (body == null) {
|
|
||||||
Log.e(TAG, "Batch sub-request for personal messages didn't contain a body")
|
|
||||||
} else {
|
|
||||||
processPersonalMessages(snode, body)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
val body = rawResponse["body"] as? RawResponse
|
||||||
|
if (body == null) {
|
||||||
|
Log.e(TAG, "Batch sub-request didn't contain a body")
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
|
processConfig(snode, body, configType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
poll(snode, deferred)
|
// the first response will be the personal messages (we want these to be processed after config messages)
|
||||||
|
val personalResponseIndex = requestSparseArray.indexOfKey(Namespace.DEFAULT())
|
||||||
|
if (personalResponseIndex >= 0) {
|
||||||
|
responseList.getOrNull(personalResponseIndex)?.let { rawResponse ->
|
||||||
|
if (rawResponse["code"] as? Int != 200) {
|
||||||
|
Log.e(TAG, "Batch sub-request for personal messages had non-200 response code, returned code ${(rawResponse["code"] as? Int) ?: "[unknown]"}")
|
||||||
|
// If we got a non-success response then the snode might be bad so we should try rotate
|
||||||
|
// to a different one just in case
|
||||||
|
pollNextSnode(deferred = deferred)
|
||||||
|
return@bind Promise.ofSuccess(Unit)
|
||||||
|
} else {
|
||||||
|
val body = rawResponse["body"] as? RawResponse
|
||||||
|
if (body == null) {
|
||||||
|
Log.e(TAG, "Batch sub-request for personal messages didn't contain a body")
|
||||||
|
} else {
|
||||||
|
processPersonalMessages(snode, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.fail {
|
|
||||||
Log.e(TAG, "Failed to get raw batch response", it)
|
|
||||||
poll(snode, deferred)
|
poll(snode, deferred)
|
||||||
}
|
}
|
||||||
|
}.fail {
|
||||||
|
Log.e(TAG, "Failed to get raw batch response", it)
|
||||||
|
poll(snode, deferred)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package org.session.libsession.snode
|
package org.session.libsession.snode
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import nl.komponents.kovenant.Deferred
|
import nl.komponents.kovenant.Deferred
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.all
|
import nl.komponents.kovenant.all
|
||||||
@ -8,6 +11,7 @@ import nl.komponents.kovenant.functional.bind
|
|||||||
import nl.komponents.kovenant.functional.map
|
import nl.komponents.kovenant.functional.map
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import org.session.libsession.messaging.file_server.FileServerApi
|
import org.session.libsession.messaging.file_server.FileServerApi
|
||||||
|
import org.session.libsession.snode.utilities.asyncPromise
|
||||||
import org.session.libsession.utilities.AESGCM
|
import org.session.libsession.utilities.AESGCM
|
||||||
import org.session.libsession.utilities.AESGCM.EncryptionResult
|
import org.session.libsession.utilities.AESGCM.EncryptionResult
|
||||||
import org.session.libsession.utilities.getBodyForOnionRequest
|
import org.session.libsession.utilities.getBodyForOnionRequest
|
||||||
@ -27,6 +31,7 @@ import org.session.libsignal.utilities.recover
|
|||||||
import org.session.libsignal.utilities.toHexString
|
import org.session.libsignal.utilities.toHexString
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
|
|
||||||
private typealias Path = List<Snode>
|
private typealias Path = List<Snode>
|
||||||
|
|
||||||
@ -112,26 +117,14 @@ object OnionRequestAPI {
|
|||||||
* Tests the given snode. The returned promise errors out if the snode is faulty; the promise is fulfilled otherwise.
|
* Tests the given snode. The returned promise errors out if the snode is faulty; the promise is fulfilled otherwise.
|
||||||
*/
|
*/
|
||||||
private fun testSnode(snode: Snode): Promise<Unit, Exception> {
|
private fun testSnode(snode: Snode): Promise<Unit, Exception> {
|
||||||
val deferred = deferred<Unit, Exception>()
|
return GlobalScope.asyncPromise { // No need to block the shared context for this
|
||||||
ThreadUtils.queue { // No need to block the shared context for this
|
|
||||||
val url = "${snode.address}:${snode.port}/get_stats/v1"
|
val url = "${snode.address}:${snode.port}/get_stats/v1"
|
||||||
try {
|
val response = HTTP.execute(HTTP.Verb.GET, url, 3).decodeToString()
|
||||||
val response = HTTP.execute(HTTP.Verb.GET, url, 3).decodeToString()
|
val json = JsonUtil.fromJson(response, Map::class.java)
|
||||||
val json = JsonUtil.fromJson(response, Map::class.java)
|
val version = json["version"] as? String
|
||||||
val version = json["version"] as? String
|
require(version != null) { "Missing snode version." }
|
||||||
if (version == null) { deferred.reject(Exception("Missing snode version.")); return@queue }
|
require(version >= "2.0.7") { "Unsupported snode version: $version." }
|
||||||
if (version >= "2.0.7") {
|
|
||||||
deferred.resolve(Unit)
|
|
||||||
} else {
|
|
||||||
val message = "Unsupported snode version: $version."
|
|
||||||
Log.d("Loki", message)
|
|
||||||
deferred.reject(Exception(message))
|
|
||||||
}
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
deferred.reject(exception)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return deferred.promise
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -359,7 +352,7 @@ object OnionRequestAPI {
|
|||||||
return@success deferred.reject(exception)
|
return@success deferred.reject(exception)
|
||||||
}
|
}
|
||||||
val destinationSymmetricKey = result.destinationSymmetricKey
|
val destinationSymmetricKey = result.destinationSymmetricKey
|
||||||
ThreadUtils.queue {
|
GlobalScope.launch {
|
||||||
try {
|
try {
|
||||||
val response = HTTP.execute(HTTP.Verb.POST, url, body)
|
val response = HTTP.execute(HTTP.Verb.POST, url, body)
|
||||||
handleResponse(response, destinationSymmetricKey, destination, version, deferred)
|
handleResponse(response, destinationSymmetricKey, destination, version, deferred)
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package org.session.libsession.snode
|
package org.session.libsession.snode
|
||||||
|
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.deferred
|
import nl.komponents.kovenant.deferred
|
||||||
import org.session.libsession.snode.OnionRequestAPI.Destination
|
import org.session.libsession.snode.OnionRequestAPI.Destination
|
||||||
|
import org.session.libsession.snode.utilities.asyncPromise
|
||||||
import org.session.libsession.utilities.AESGCM
|
import org.session.libsession.utilities.AESGCM
|
||||||
import org.session.libsession.utilities.AESGCM.EncryptionResult
|
import org.session.libsession.utilities.AESGCM.EncryptionResult
|
||||||
import org.session.libsignal.utilities.toHexString
|
import org.session.libsignal.utilities.toHexString
|
||||||
@ -37,68 +39,56 @@ object OnionRequestEncryption {
|
|||||||
destination: Destination,
|
destination: Destination,
|
||||||
version: Version
|
version: Version
|
||||||
): Promise<EncryptionResult, Exception> {
|
): Promise<EncryptionResult, Exception> {
|
||||||
val deferred = deferred<EncryptionResult, Exception>()
|
return GlobalScope.asyncPromise {
|
||||||
ThreadUtils.queue {
|
val plaintext = if (version == Version.V4) {
|
||||||
try {
|
payload
|
||||||
val plaintext = if (version == Version.V4) {
|
} else {
|
||||||
payload
|
// Wrapping isn't needed for file server or open group onion requests
|
||||||
} else {
|
when (destination) {
|
||||||
// Wrapping isn't needed for file server or open group onion requests
|
is Destination.Snode -> encode(payload, mapOf("headers" to ""))
|
||||||
when (destination) {
|
is Destination.Server -> payload
|
||||||
is Destination.Snode -> encode(payload, mapOf("headers" to ""))
|
|
||||||
is Destination.Server -> payload
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val x25519PublicKey = when (destination) {
|
|
||||||
is Destination.Snode -> destination.snode.publicKeySet!!.x25519Key
|
|
||||||
is Destination.Server -> destination.x25519PublicKey
|
|
||||||
}
|
|
||||||
val result = AESGCM.encrypt(plaintext, x25519PublicKey)
|
|
||||||
deferred.resolve(result)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
deferred.reject(exception)
|
|
||||||
}
|
}
|
||||||
|
val x25519PublicKey = when (destination) {
|
||||||
|
is Destination.Snode -> destination.snode.publicKeySet!!.x25519Key
|
||||||
|
is Destination.Server -> destination.x25519PublicKey
|
||||||
|
}
|
||||||
|
AESGCM.encrypt(plaintext, x25519PublicKey)
|
||||||
}
|
}
|
||||||
return deferred.promise
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypts the previous encryption result (i.e. that of the hop after this one) for this hop. Use this to build the layers of an onion request.
|
* Encrypts the previous encryption result (i.e. that of the hop after this one) for this hop. Use this to build the layers of an onion request.
|
||||||
*/
|
*/
|
||||||
internal fun encryptHop(lhs: Destination, rhs: Destination, previousEncryptionResult: EncryptionResult): Promise<EncryptionResult, Exception> {
|
internal fun encryptHop(lhs: Destination, rhs: Destination, previousEncryptionResult: EncryptionResult): Promise<EncryptionResult, Exception> {
|
||||||
val deferred = deferred<EncryptionResult, Exception>()
|
return GlobalScope.asyncPromise {
|
||||||
ThreadUtils.queue {
|
val payload: MutableMap<String, Any> = when (rhs) {
|
||||||
try {
|
is Destination.Snode -> {
|
||||||
val payload: MutableMap<String, Any> = when (rhs) {
|
mutableMapOf("destination" to rhs.snode.publicKeySet!!.ed25519Key)
|
||||||
is Destination.Snode -> {
|
|
||||||
mutableMapOf( "destination" to rhs.snode.publicKeySet!!.ed25519Key )
|
|
||||||
}
|
|
||||||
is Destination.Server -> {
|
|
||||||
mutableMapOf(
|
|
||||||
"host" to rhs.host,
|
|
||||||
"target" to rhs.target,
|
|
||||||
"method" to "POST",
|
|
||||||
"protocol" to rhs.scheme,
|
|
||||||
"port" to rhs.port
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
payload["ephemeral_key"] = previousEncryptionResult.ephemeralPublicKey.toHexString()
|
|
||||||
val x25519PublicKey = when (lhs) {
|
is Destination.Server -> {
|
||||||
is Destination.Snode -> {
|
mutableMapOf(
|
||||||
lhs.snode.publicKeySet!!.x25519Key
|
"host" to rhs.host,
|
||||||
}
|
"target" to rhs.target,
|
||||||
is Destination.Server -> {
|
"method" to "POST",
|
||||||
lhs.x25519PublicKey
|
"protocol" to rhs.scheme,
|
||||||
}
|
"port" to rhs.port
|
||||||
|
)
|
||||||
}
|
}
|
||||||
val plaintext = encode(previousEncryptionResult.ciphertext, payload)
|
|
||||||
val result = AESGCM.encrypt(plaintext, x25519PublicKey)
|
|
||||||
deferred.resolve(result)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
deferred.reject(exception)
|
|
||||||
}
|
}
|
||||||
|
payload["ephemeral_key"] = previousEncryptionResult.ephemeralPublicKey.toHexString()
|
||||||
|
val x25519PublicKey = when (lhs) {
|
||||||
|
is Destination.Snode -> {
|
||||||
|
lhs.snode.publicKeySet!!.x25519Key
|
||||||
|
}
|
||||||
|
|
||||||
|
is Destination.Server -> {
|
||||||
|
lhs.x25519PublicKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val plaintext = encode(previousEncryptionResult.ciphertext, payload)
|
||||||
|
AESGCM.encrypt(plaintext, x25519PublicKey)
|
||||||
}
|
}
|
||||||
return deferred.promise
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import com.goterl.lazysodium.interfaces.GenericHash
|
|||||||
import com.goterl.lazysodium.interfaces.PwHash
|
import com.goterl.lazysodium.interfaces.PwHash
|
||||||
import com.goterl.lazysodium.interfaces.SecretBox
|
import com.goterl.lazysodium.interfaces.SecretBox
|
||||||
import com.goterl.lazysodium.utils.Key
|
import com.goterl.lazysodium.utils.Key
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
@ -27,6 +28,7 @@ import nl.komponents.kovenant.unwrap
|
|||||||
import org.session.libsession.messaging.utilities.MessageWrapper
|
import org.session.libsession.messaging.utilities.MessageWrapper
|
||||||
import org.session.libsession.messaging.utilities.SodiumUtilities.sodium
|
import org.session.libsession.messaging.utilities.SodiumUtilities.sodium
|
||||||
import org.session.libsession.snode.model.BatchResponse
|
import org.session.libsession.snode.model.BatchResponse
|
||||||
|
import org.session.libsession.snode.utilities.asyncPromise
|
||||||
import org.session.libsession.snode.utilities.await
|
import org.session.libsession.snode.utilities.await
|
||||||
import org.session.libsession.snode.utilities.retrySuspendAsPromise
|
import org.session.libsession.snode.utilities.retrySuspendAsPromise
|
||||||
import org.session.libsession.utilities.mapValuesNotNull
|
import org.session.libsession.utilities.mapValuesNotNull
|
||||||
@ -137,7 +139,7 @@ object SnodeAPI {
|
|||||||
JsonUtil.fromJson(it.body ?: throw Error.Generic, Map::class.java)
|
JsonUtil.fromJson(it.body ?: throw Error.Generic, Map::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> task {
|
else -> GlobalScope.asyncPromise {
|
||||||
HTTP.execute(
|
HTTP.execute(
|
||||||
HTTP.Verb.POST,
|
HTTP.Verb.POST,
|
||||||
url = "${snode.address}:${snode.port}/storage_rpc/v1",
|
url = "${snode.address}:${snode.port}/storage_rpc/v1",
|
||||||
@ -169,17 +171,15 @@ object SnodeAPI {
|
|||||||
JsonUtil.fromJson(resp.body ?: throw Error.Generic, responseClass)
|
JsonUtil.fromJson(resp.body ?: throw Error.Generic, responseClass)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> withContext(Dispatchers.IO) {
|
else -> HTTP.execute(
|
||||||
HTTP.execute(
|
HTTP.Verb.POST,
|
||||||
HTTP.Verb.POST,
|
url = "${snode.address}:${snode.port}/storage_rpc/v1",
|
||||||
url = "${snode.address}:${snode.port}/storage_rpc/v1",
|
parameters = buildMap {
|
||||||
parameters = buildMap {
|
this["method"] = method.rawValue
|
||||||
this["method"] = method.rawValue
|
this["params"] = parameters
|
||||||
this["params"] = parameters
|
|
||||||
}
|
|
||||||
).toString().let {
|
|
||||||
JsonUtil.fromJson(it, responseClass)
|
|
||||||
}
|
}
|
||||||
|
).toString().let {
|
||||||
|
JsonUtil.fromJson(it, responseClass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +192,7 @@ object SnodeAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun getRandomSnode(): Promise<Snode, Exception> =
|
internal fun getRandomSnode(): Promise<Snode, Exception> =
|
||||||
snodePool.takeIf { it.size >= minimumSnodePoolCount }?.secureRandom()?.let { Promise.of(it) } ?: task {
|
snodePool.takeIf { it.size >= minimumSnodePoolCount }?.secureRandom()?.let { Promise.of(it) } ?: GlobalScope.asyncPromise {
|
||||||
val target = seedNodePool.random()
|
val target = seedNodePool.random()
|
||||||
Log.d("Loki", "Populating snode pool using: $target.")
|
Log.d("Loki", "Populating snode pool using: $target.")
|
||||||
val url = "$target/json_rpc"
|
val url = "$target/json_rpc"
|
||||||
@ -241,7 +241,7 @@ object SnodeAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Public API
|
// Public API
|
||||||
fun getAccountID(onsName: String): Promise<String, Exception> = task {
|
fun getAccountID(onsName: String): Promise<String, Exception> = GlobalScope.asyncPromise {
|
||||||
val validationCount = 3
|
val validationCount = 3
|
||||||
val accountIDByteCount = 33
|
val accountIDByteCount = 33
|
||||||
// Hash the ONS name using BLAKE2b
|
// Hash the ONS name using BLAKE2b
|
||||||
@ -630,11 +630,16 @@ object SnodeAPI {
|
|||||||
getBatchResponse(
|
getBatchResponse(
|
||||||
snode = snode,
|
snode = snode,
|
||||||
publicKey = batch.first().publicKey,
|
publicKey = batch.first().publicKey,
|
||||||
requests = batch.map { it.request }, sequence = false
|
requests = batch.mapNotNull { info ->
|
||||||
|
info.request.takeIf { !info.callback.isClosedForSend }
|
||||||
|
},
|
||||||
|
sequence = false
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
for (req in batch) {
|
for (req in batch) {
|
||||||
req.callback.send(Result.failure(e))
|
runCatching {
|
||||||
|
req.callback.send(Result.failure(e))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return@batch
|
return@batch
|
||||||
}
|
}
|
||||||
@ -650,7 +655,9 @@ object SnodeAPI {
|
|||||||
JsonUtil.fromJson(resp.body, req.responseType)
|
JsonUtil.fromJson(resp.body, req.responseType)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.callback.send(result)
|
runCatching{
|
||||||
|
req.callback.send(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close all channels in the requests just in case we don't have paired up
|
// Close all channels in the requests just in case we don't have paired up
|
||||||
@ -673,7 +680,14 @@ object SnodeAPI {
|
|||||||
val callback = Channel<Result<T>>()
|
val callback = Channel<Result<T>>()
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
batchedRequestsSender.send(RequestInfo(snode, publicKey, request, responseType, callback as SendChannel<Any>))
|
batchedRequestsSender.send(RequestInfo(snode, publicKey, request, responseType, callback as SendChannel<Any>))
|
||||||
return callback.receive().getOrThrow()
|
try {
|
||||||
|
return callback.receive().getOrThrow()
|
||||||
|
} catch (e: CancellationException) {
|
||||||
|
// Close the channel if the coroutine is cancelled, so the batch processing won't
|
||||||
|
// handle this one (best effort only)
|
||||||
|
callback.close()
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendBatchRequest(
|
suspend fun sendBatchRequest(
|
||||||
|
@ -2,10 +2,14 @@ package org.session.libsession.snode.utilities
|
|||||||
|
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.deferred
|
import nl.komponents.kovenant.deferred
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.resumeWithException
|
import kotlin.coroutines.resumeWithException
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
@ -17,9 +21,20 @@ suspend fun <T, E: Throwable> Promise<T, E>.await(): T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> CoroutineScope.asyncPromise(block: suspend () -> T): Promise<T, Exception> {
|
fun <V, E: Throwable> Promise<V, E>.successBackground(callback: (value: V) -> Unit): Promise<V, E> {
|
||||||
|
GlobalScope.launch {
|
||||||
|
try {
|
||||||
|
callback(this@successBackground.await())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d("Loki", "Failed to execute task in background: ${e.message}.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> CoroutineScope.asyncPromise(context: CoroutineContext = EmptyCoroutineContext, block: suspend () -> T): Promise<T, Exception> {
|
||||||
val defer = deferred<T, Exception>()
|
val defer = deferred<T, Exception>()
|
||||||
launch {
|
launch(context) {
|
||||||
try {
|
try {
|
||||||
defer.resolve(block())
|
defer.resolve(block())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package org.session.libsession.utilities
|
package org.session.libsession.utilities
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import org.session.libsession.messaging.file_server.FileServerApi
|
import org.session.libsession.messaging.file_server.FileServerApi
|
||||||
|
import org.session.libsession.snode.utilities.await
|
||||||
import org.session.libsignal.utilities.HTTP
|
import org.session.libsignal.utilities.HTTP
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -14,8 +17,7 @@ object DownloadUtilities {
|
|||||||
/**
|
/**
|
||||||
* Blocks the calling thread.
|
* Blocks the calling thread.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
suspend fun downloadFile(destination: File, url: String) {
|
||||||
fun downloadFile(destination: File, url: String) {
|
|
||||||
val outputStream = FileOutputStream(destination) // Throws
|
val outputStream = FileOutputStream(destination) // Throws
|
||||||
var remainingAttempts = 2
|
var remainingAttempts = 2
|
||||||
var exception: Exception? = null
|
var exception: Exception? = null
|
||||||
@ -35,13 +37,13 @@ object DownloadUtilities {
|
|||||||
/**
|
/**
|
||||||
* Blocks the calling thread.
|
* Blocks the calling thread.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
suspend fun downloadFile(outputStream: OutputStream, urlAsString: String) {
|
||||||
fun downloadFile(outputStream: OutputStream, urlAsString: String) {
|
|
||||||
val url = urlAsString.toHttpUrlOrNull()!!
|
val url = urlAsString.toHttpUrlOrNull()!!
|
||||||
val fileID = url.pathSegments.last()
|
val fileID = url.pathSegments.last()
|
||||||
try {
|
try {
|
||||||
FileServerApi.download(fileID).get().let {
|
val data = FileServerApi.download(fileID).await()
|
||||||
outputStream.write(it)
|
withContext(Dispatchers.IO) {
|
||||||
|
outputStream.write(data)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
when (e) {
|
when (e) {
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
package org.session.libsignal.utilities
|
package org.session.libsignal.utilities
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import okhttp3.Call
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
|
import okhttp3.Response
|
||||||
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@ -81,14 +85,14 @@ object HTTP {
|
|||||||
/**
|
/**
|
||||||
* Sync. Don't call from the main thread.
|
* Sync. Don't call from the main thread.
|
||||||
*/
|
*/
|
||||||
fun execute(verb: Verb, url: String, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): ByteArray {
|
suspend fun execute(verb: Verb, url: String, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): ByteArray {
|
||||||
return execute(verb = verb, url = url, body = null, timeout = timeout, useSeedNodeConnection = useSeedNodeConnection)
|
return execute(verb = verb, url = url, body = null, timeout = timeout, useSeedNodeConnection = useSeedNodeConnection)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync. Don't call from the main thread.
|
* Sync. Don't call from the main thread.
|
||||||
*/
|
*/
|
||||||
fun execute(verb: Verb, url: String, parameters: Map<String, Any>?, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): ByteArray {
|
suspend fun execute(verb: Verb, url: String, parameters: Map<String, Any>?, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): ByteArray {
|
||||||
return if (parameters != null) {
|
return if (parameters != null) {
|
||||||
val body = JsonUtil.toJson(parameters).toByteArray()
|
val body = JsonUtil.toJson(parameters).toByteArray()
|
||||||
execute(verb = verb, url = url, body = body, timeout = timeout, useSeedNodeConnection = useSeedNodeConnection)
|
execute(verb = verb, url = url, body = body, timeout = timeout, useSeedNodeConnection = useSeedNodeConnection)
|
||||||
@ -100,7 +104,7 @@ object HTTP {
|
|||||||
/**
|
/**
|
||||||
* Sync. Don't call from the main thread.
|
* Sync. Don't call from the main thread.
|
||||||
*/
|
*/
|
||||||
fun execute(verb: Verb, url: String, body: ByteArray?, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): ByteArray {
|
suspend fun execute(verb: Verb, url: String, body: ByteArray?, timeout: Long = HTTP.timeout, useSeedNodeConnection: Boolean = false): ByteArray {
|
||||||
val request = Request.Builder().url(url)
|
val request = Request.Builder().url(url)
|
||||||
.removeHeader("User-Agent").addHeader("User-Agent", "WhatsApp") // Set a fake value
|
.removeHeader("User-Agent").addHeader("User-Agent", "WhatsApp") // Set a fake value
|
||||||
.removeHeader("Accept-Language").addHeader("Accept-Language", "en-us") // Set a fake value
|
.removeHeader("Accept-Language").addHeader("Accept-Language", "en-us") // Set a fake value
|
||||||
@ -125,7 +129,7 @@ object HTTP {
|
|||||||
}
|
}
|
||||||
useSeedNodeConnection -> seedNodeConnection
|
useSeedNodeConnection -> seedNodeConnection
|
||||||
else -> defaultConnection
|
else -> defaultConnection
|
||||||
}.newCall(request.build()).execute().use { response ->
|
}.newCall(request.build()).await().use { response ->
|
||||||
when (val statusCode = response.code) {
|
when (val statusCode = response.code) {
|
||||||
200 -> response.body!!.bytes()
|
200 -> response.body!!.bytes()
|
||||||
else -> {
|
else -> {
|
||||||
@ -143,4 +147,13 @@ object HTTP {
|
|||||||
throw HTTPRequestFailedException(0, null, "HTTP request failed due to: ${exception.message}")
|
throw HTTPRequestFailedException(0, null, "HTTP request failed due to: ${exception.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("OPT_IN_USAGE")
|
||||||
|
private val httpCallDispatcher = Dispatchers.IO.limitedParallelism(3)
|
||||||
|
|
||||||
|
private suspend fun Call.await(): Response {
|
||||||
|
return withContext(httpCallDispatcher) {
|
||||||
|
execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,19 +4,9 @@ package org.session.libsignal.utilities
|
|||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.deferred
|
import nl.komponents.kovenant.deferred
|
||||||
import nl.komponents.kovenant.functional.map
|
import nl.komponents.kovenant.functional.map
|
||||||
import nl.komponents.kovenant.task
|
|
||||||
import java.util.concurrent.TimeoutException
|
|
||||||
|
|
||||||
fun emptyPromise() = EMPTY_PROMISE
|
fun emptyPromise() = Promise.of(Unit)
|
||||||
private val EMPTY_PROMISE: Promise<*, java.lang.Exception> = task {}
|
|
||||||
|
|
||||||
fun <V, E : Throwable> Promise<V, E>.get(defaultValue: V): V {
|
|
||||||
return try {
|
|
||||||
get()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
defaultValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <V, E : Throwable> Promise<V, E>.recover(callback: (exception: E) -> V): Promise<V, E> {
|
fun <V, E : Throwable> Promise<V, E>.recover(callback: (exception: E) -> V): Promise<V, E> {
|
||||||
val deferred = deferred<V, E>()
|
val deferred = deferred<V, E>()
|
||||||
@ -33,33 +23,6 @@ fun <V, E : Throwable> Promise<V, E>.recover(callback: (exception: E) -> V): Pro
|
|||||||
return deferred.promise
|
return deferred.promise
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <V, E> Promise<V, E>.successBackground(callback: (value: V) -> Unit): Promise<V, E> {
|
|
||||||
ThreadUtils.queue {
|
|
||||||
try {
|
|
||||||
callback(get())
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.d("Loki", "Failed to execute task in background: ${e.message}.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <V> Promise<V, Exception>.timeout(millis: Long): Promise<V, Exception> {
|
|
||||||
if (this.isDone()) { return this; }
|
|
||||||
val deferred = deferred<V, Exception>()
|
|
||||||
ThreadUtils.queue {
|
|
||||||
Thread.sleep(millis)
|
|
||||||
if (!deferred.promise.isDone()) {
|
|
||||||
deferred.reject(TimeoutException("Promise timed out."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.success {
|
|
||||||
if (!deferred.promise.isDone()) { deferred.resolve(it) }
|
|
||||||
}.fail {
|
|
||||||
if (!deferred.promise.isDone()) { deferred.reject(it) }
|
|
||||||
}
|
|
||||||
return deferred.promise
|
|
||||||
}
|
|
||||||
|
|
||||||
infix fun <V, E: Exception> Promise<V, E>.sideEffect(
|
infix fun <V, E: Exception> Promise<V, E>.sideEffect(
|
||||||
callback: (value: V) -> Unit
|
callback: (value: V) -> Unit
|
||||||
|
@ -14,16 +14,10 @@ object ThreadUtils {
|
|||||||
|
|
||||||
const val PRIORITY_IMPORTANT_BACKGROUND_THREAD = Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE
|
const val PRIORITY_IMPORTANT_BACKGROUND_THREAD = Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE
|
||||||
|
|
||||||
// Note: To see how many threads are running in our app at any given time we can use:
|
@Deprecated("Use a proper coroutine context/dispatcher instead, so it's clearer what priority you want the work to be done")
|
||||||
// val threadCount = getAllStackTraces().size
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun queue(target: Runnable) {
|
|
||||||
queue(target::run)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun queue(target: () -> Unit) {
|
fun queue(target: () -> Unit) {
|
||||||
Dispatchers.IO.dispatch(EmptyCoroutineContext) {
|
Dispatchers.Default.dispatch(EmptyCoroutineContext) {
|
||||||
try {
|
try {
|
||||||
target()
|
target()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Loading…
Reference in New Issue
Block a user