Getting rid of .get call on promise

This commit is contained in:
SessionHero01 2024-10-02 11:25:37 +10:00
parent 45a66d0eea
commit 3faae5ddbe
No known key found for this signature in database
27 changed files with 363 additions and 344 deletions

View File

@ -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

View File

@ -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)
} }
} }
} }

View File

@ -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) {
OpenGroupManager.add(
server = openGroup.server,
room = openGroup.room,
publicKey = openGroup.serverPublicKey,
context = activity
)
storage.onOpenGroupAdded(openGroup.server, openGroup.room) 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()
} }
} }

View File

@ -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

View File

@ -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) { if (changed) {
_configUpdateNotifications.tryEmit(ConfigUpdateNotification.UserConfigs) _configUpdateNotifications.tryEmit(ConfigUpdateNotification.UserConfigs)
} }
result return result
}
} }
override fun mergeUserConfigs( override fun mergeUserConfigs(
@ -319,8 +319,9 @@ 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") Log.d("ConfigFactory", "Group updated? $groupId: $changed")
@ -330,8 +331,7 @@ class ConfigFactory @Inject constructor(
} }
} }
result return result
}
} }
override fun <T> withMutableGroupConfigs( override fun <T> withMutableGroupConfigs(

View File

@ -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)
} }
} }

View File

@ -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) } }
} }

View File

@ -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();
} }

View File

@ -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

View File

@ -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)) {

View File

@ -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,34 +49,37 @@ 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 {
userMutex.withLock {
pushUserConfigChangesIfNeeded() pushUserConfigChangesIfNeeded()
} }
} }
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Error handling config update", e) Log.e(TAG, "Error handling config update", e)
} }

View File

@ -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))

View File

@ -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()) {

View File

@ -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> {

View File

@ -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)
} }
} }

View File

@ -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) {

View File

@ -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")

View File

@ -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

View File

@ -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,8 +235,7 @@ 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
@ -349,6 +347,5 @@ class Poller(
} }
} }
} }
}
// endregion // endregion
} }

View File

@ -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
if (version == null) { deferred.reject(Exception("Missing snode version.")); return@queue } require(version != null) { "Missing snode version." }
if (version >= "2.0.7") { require(version >= "2.0.7") { "Unsupported snode version: $version." }
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)

View File

@ -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,9 +39,7 @@ 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 {
try {
val plaintext = if (version == Version.V4) { val plaintext = if (version == Version.V4) {
payload payload
} else { } else {
@ -53,26 +53,20 @@ object OnionRequestEncryption {
is Destination.Snode -> destination.snode.publicKeySet!!.x25519Key is Destination.Snode -> destination.snode.publicKeySet!!.x25519Key
is Destination.Server -> destination.x25519PublicKey is Destination.Server -> destination.x25519PublicKey
} }
val result = AESGCM.encrypt(plaintext, x25519PublicKey) AESGCM.encrypt(plaintext, x25519PublicKey)
deferred.resolve(result)
} catch (exception: Exception) {
deferred.reject(exception)
} }
} }
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 {
try {
val payload: MutableMap<String, Any> = when (rhs) { val payload: MutableMap<String, Any> = when (rhs) {
is Destination.Snode -> { is Destination.Snode -> {
mutableMapOf("destination" to rhs.snode.publicKeySet!!.ed25519Key) mutableMapOf("destination" to rhs.snode.publicKeySet!!.ed25519Key)
} }
is Destination.Server -> { is Destination.Server -> {
mutableMapOf( mutableMapOf(
"host" to rhs.host, "host" to rhs.host,
@ -88,17 +82,13 @@ object OnionRequestEncryption {
is Destination.Snode -> { is Destination.Snode -> {
lhs.snode.publicKeySet!!.x25519Key lhs.snode.publicKeySet!!.x25519Key
} }
is Destination.Server -> { is Destination.Server -> {
lhs.x25519PublicKey lhs.x25519PublicKey
} }
} }
val plaintext = encode(previousEncryptionResult.ciphertext, payload) val plaintext = encode(previousEncryptionResult.ciphertext, payload)
val result = AESGCM.encrypt(plaintext, x25519PublicKey) AESGCM.encrypt(plaintext, x25519PublicKey)
deferred.resolve(result)
} catch (exception: Exception) {
deferred.reject(exception)
} }
} }
return deferred.promise
}
} }

View File

@ -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,8 +171,7 @@ 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 {
@ -181,7 +182,6 @@ object SnodeAPI {
JsonUtil.fromJson(it, responseClass) JsonUtil.fromJson(it, responseClass)
} }
} }
}
private val GET_RANDOM_SNODE_PARAMS = buildMap<String, Any> { private val GET_RANDOM_SNODE_PARAMS = buildMap<String, Any> {
this["method"] = "get_n_service_nodes" this["method"] = "get_n_service_nodes"
@ -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,12 +630,17 @@ 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) {
runCatching {
req.callback.send(Result.failure(e)) req.callback.send(Result.failure(e))
} }
}
return@batch return@batch
} }
@ -650,8 +655,10 @@ object SnodeAPI {
JsonUtil.fromJson(resp.body, req.responseType) JsonUtil.fromJson(resp.body, req.responseType)
} }
runCatching{
req.callback.send(result) 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
// responses. // responses.
@ -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>))
try {
return callback.receive().getOrThrow() 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(

View File

@ -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) {

View File

@ -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) {

View File

@ -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()
}
}
} }

View File

@ -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

View File

@ -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) {