diff --git a/app/build.gradle b/app/build.gradle index 1ec3744ee..64b407894 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -69,6 +69,7 @@ dependencies { implementation 'se.emilsjolander:stickylistheaders:2.7.0' implementation 'com.jpardogo.materialtabstrip:library:1.0.9' implementation 'org.apache.httpcomponents:httpclient-android:4.3.5' + implementation 'commons-net:commons-net:3.7.2' implementation 'com.github.chrisbanes:PhotoView:2.1.3' implementation 'com.github.bumptech.glide:glide:4.11.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt index a25d3157e..b685a73de 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt @@ -51,6 +51,7 @@ import org.session.libsignal.service.loki.protocol.meta.SessionMetaProtocol import org.session.libsignal.service.loki.protocol.sessionmanagement.SessionManagementProtocol import org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol import org.session.libsignal.service.loki.protocol.shelved.syncmessages.SyncMessagesProtocol +import org.session.libsignal.service.loki.utilities.ThreadUtils import org.session.libsignal.service.loki.utilities.toHexString import org.thoughtcrime.securesms.loki.dialogs.* import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocolV2 @@ -298,13 +299,13 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe .setMessage(R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact) .setNegativeButton(android.R.string.cancel, null) .setPositiveButton(R.string.RecipientPreferenceActivity_block) { dialog, _ -> - Thread { + ThreadUtils.queue { DatabaseFactory.getRecipientDatabase(this).setBlocked(thread.recipient, true) Util.runOnMain { recyclerView.adapter!!.notifyDataSetChanged() dialog.dismiss() } - }.start() + } }.show() } @@ -314,13 +315,13 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe .setMessage(R.string.RecipientPreferenceActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact) .setNegativeButton(android.R.string.cancel, null) .setPositiveButton(R.string.RecipientPreferenceActivity_unblock) { dialog, _ -> - Thread { + ThreadUtils.queue { DatabaseFactory.getRecipientDatabase(this).setBlocked(thread.recipient, false) Util.runOnMain { recyclerView.adapter!!.notifyDataSetChanged() dialog.dismiss() } - }.start() + } }.show() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt index 7b064d88d..57e8515a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocolV2.kt @@ -12,6 +12,7 @@ import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.internal.push.SignalServiceProtos +import org.session.libsignal.service.loki.utilities.ThreadUtils import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.toHexString @@ -29,7 +30,6 @@ import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.service.loki.utilities.ThreadUtils import java.io.IOException import java.util.* diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/IP2Country.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/IP2Country.kt index 399f6c20f..e2a64eb65 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/IP2Country.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/utilities/IP2Country.kt @@ -8,6 +8,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager import org.thoughtcrime.securesms.logging.Log import com.opencsv.CSVReader import org.session.libsignal.service.loki.api.onionrequests.OnionRequestAPI +import org.session.libsignal.service.loki.utilities.ThreadUtils import java.io.File import java.io.FileOutputStream import java.io.FileReader @@ -16,12 +17,33 @@ class IP2Country private constructor(private val context: Context) { private val pathsBuiltEventReceiver: BroadcastReceiver val countryNamesCache = mutableMapOf() - private val ipv4Table by lazy { - loadFile("geolite2_country_blocks_ipv4.csv") + private fun Ipv4Int(ip:String) = ip.takeWhile { it != '/' }.split('.').foldIndexed(0L) { i, acc, s -> + val asInt = s.toLong() + acc + (asInt shl (8 * (3-i))) } - private val countryNamesTable by lazy { - loadFile("geolite2_country_locations_english.csv") + private val ipv4ToCountry by lazy { + val file = loadFile("geolite2_country_blocks_ipv4.csv") + val csv = CSVReader(FileReader(file.absoluteFile)).apply { + skip(1) + } + + csv.readAll() + .associate { cols -> + Ipv4Int(cols[0]) to cols[1].toIntOrNull() + } + } + + private val countryToNames by lazy { + val file = loadFile("geolite2_country_locations_english.csv") + val csv = CSVReader(FileReader(file.absoluteFile)).apply { + skip(1) + } + csv.readAll() + .filter { cols -> !cols[0].isNullOrEmpty() && !cols[1].isNullOrEmpty() } + .associate { cols -> + cols[0].toInt() to cols[5] + } } // region Initialization @@ -40,7 +62,6 @@ class IP2Country private constructor(private val context: Context) { init { populateCacheIfNeeded() pathsBuiltEventReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { populateCacheIfNeeded() } @@ -69,51 +90,40 @@ class IP2Country private constructor(private val context: Context) { return file } - private fun cacheCountryForIP(ip: String): String { - var truncatedIP = ip - fun getCountryInternal(): String { - val country = countryNamesCache[ip] - if (country != null) { return country } - val ipv4TableReader = CSVReader(FileReader(ipv4Table.absoluteFile)) - val countryNamesTableReader = CSVReader(FileReader(countryNamesTable.absoluteFile)) - var ipv4TableLine = ipv4TableReader.readNext() - while (ipv4TableLine != null) { - if (!ipv4TableLine[0].startsWith(truncatedIP)) { - ipv4TableLine = ipv4TableReader.readNext() - continue - } - val countryID = ipv4TableLine[1] - var countryNamesTableLine = countryNamesTableReader.readNext() - while (countryNamesTableLine != null) { - if (countryNamesTableLine[0] != countryID) { - countryNamesTableLine = countryNamesTableReader.readNext() - continue - } - @Suppress("NAME_SHADOWING") val country = countryNamesTableLine[5] - countryNamesCache[ip] = country - return country - } - } - if (truncatedIP.contains(".") && !truncatedIP.endsWith(".")) { // The fuzziest we want to go is xxx.x - truncatedIP = truncatedIP.dropLast(1) - if (truncatedIP.endsWith(".")) { truncatedIP = truncatedIP.dropLast(1) } - return getCountryInternal() + private fun cacheCountryForIP(ip: String): String? { + + // return early if cached + countryNamesCache[ip]?.let { return it } + + val comps = ipv4ToCountry.asSequence() + + val bestMatchCountry = comps.lastOrNull { it.key <= Ipv4Int(ip) }?.let { (_, code) -> + if (code != null) { + countryToNames[code] } else { - return "Unknown Country" + null } } - return getCountryInternal() + + if (bestMatchCountry != null) { + countryNamesCache[ip] = bestMatchCountry + return bestMatchCountry + } else { + Log.d("Loki","Country name for $ip couldn't be found") + } + return null } private fun populateCacheIfNeeded() { - Thread { - val path = OnionRequestAPI.paths.firstOrNull() ?: return@Thread - path.forEach { snode -> - cacheCountryForIP(snode.ip) // Preload if needed + ThreadUtils.queue { + OnionRequestAPI.paths.forEach { path -> + path.forEach { snode -> + cacheCountryForIP(snode.ip) // Preload if needed + } } Broadcaster(context).broadcast("onionRequestPathCountriesLoaded") Log.d("Loki", "Finished preloading onion request path countries.") - }.start() + } } // endregion } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java index 12aabaf32..3e6338388 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java @@ -6,18 +6,15 @@ import android.os.Looper; import androidx.annotation.MainThread; import androidx.annotation.NonNull; -import org.jetbrains.annotations.NotNull; -import org.session.libsignal.service.api.messages.SignalServiceGroup; -import org.session.libsignal.service.internal.push.SignalServiceProtos; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.loki.api.PublicChatManager; import org.thoughtcrime.securesms.util.Debouncer; import org.session.libsignal.service.loki.api.Poller; +import org.session.libsignal.service.loki.utilities.ThreadUtils; import org.session.libsession.messaging.threads.recipients.Recipient; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; -import java.util.Collection; import java.util.concurrent.TimeUnit; public class OptimizedMessageNotifier implements MessageNotifier { @@ -129,7 +126,7 @@ public class OptimizedMessageNotifier implements MessageNotifier { private void performOnBackgroundThreadIfNeeded(Runnable r) { if (Looper.myLooper() == Looper.getMainLooper()) { - new Thread(r).start(); + ThreadUtils.queue(r); } else { r.run(); } diff --git a/libsession/src/main/java/org/session/libsession/utilities/PromiseUtilities.kt b/libsession/src/main/java/org/session/libsession/utilities/PromiseUtilities.kt index b3c5cd918..50a70aa47 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/PromiseUtilities.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/PromiseUtilities.kt @@ -45,25 +45,25 @@ fun Promise.recover(callback: (exception: E) -> V): Pro } fun Promise.successBackground(callback: (value: V) -> Unit): Promise { - Thread { + ThreadUtils.queue { try { callback(get()) } catch (e: Exception) { Log.d("Loki", "Failed to execute task in background: ${e.message}.") } - }.start() + } return this } fun Promise.timeout(millis: Long): Promise { if (this.isDone()) { return this; } val deferred = deferred() - Thread { + ThreadUtils.queue { Thread.sleep(millis) if (!deferred.promise.isDone()) { deferred.reject(TimeoutException("Promise timed out.")) } - }.start() + } this.success { if (!deferred.promise.isDone()) { deferred.resolve(it) } }.fail {