@@ -12,10 +12,8 @@ import kotlinx.coroutines.*
|
|||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.IOException
|
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class generates a default nickname for new clients.
|
* This class generates a default nickname for new clients.
|
||||||
@@ -28,63 +26,54 @@ object MacLookup {
|
|||||||
override fun getLocalizedMessage() = formatMessage(app)
|
override fun getLocalizedMessage() = formatMessage(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class Work(@Volatile var conn: HttpURLConnection, var job: Job? = null)
|
private val macLookupBusy = mutableMapOf<MacAddressCompat, Pair<HttpURLConnection, Job>>()
|
||||||
private val macLookupBusy = mutableMapOf<MacAddressCompat, Work>()
|
private val countryCodeRegex = "([A-Z]{2})\\s*\$".toRegex() // http://en.wikipedia.org/wiki/ISO_3166-1
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
fun abort(mac: MacAddressCompat) = macLookupBusy.remove(mac)?.let { work ->
|
fun abort(mac: MacAddressCompat) = macLookupBusy.remove(mac)?.let { (conn, job) ->
|
||||||
work.job!!.cancel()
|
job.cancel()
|
||||||
if (Build.VERSION.SDK_INT < 26) GlobalScope.launch(Dispatchers.IO) {
|
if (Build.VERSION.SDK_INT < 26) GlobalScope.launch(Dispatchers.IO) {
|
||||||
work.conn.disconnect()
|
conn.disconnect()
|
||||||
} else work.conn.disconnect()
|
} else conn.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
fun perform(mac: MacAddressCompat, explicit: Boolean = false) {
|
fun perform(mac: MacAddressCompat, explicit: Boolean = false) {
|
||||||
abort(mac)
|
abort(mac)
|
||||||
val url = URL("https://api.maclookup.app/v2/macs/${mac.toOui()}")
|
val conn = URL("https://macvendors.co/api/$mac").openConnection() as HttpURLConnection
|
||||||
val work = Work(url.openConnection() as HttpURLConnection)
|
macLookupBusy[mac] = conn to GlobalScope.launch(Dispatchers.IO) {
|
||||||
work.job = GlobalScope.launch(Dispatchers.IO) {
|
|
||||||
try {
|
try {
|
||||||
var response: String
|
val response = conn.inputStream.bufferedReader().readText()
|
||||||
var obj: JSONObject
|
val obj = JSONObject(response).getJSONObject("result")
|
||||||
while (true) {
|
obj.opt("error")?.also { throw UnexpectedError(mac, it.toString()) }
|
||||||
response = work.conn.inputStream.bufferedReader().readText()
|
|
||||||
obj = JSONObject(response)
|
|
||||||
if (obj.getBoolean("success")) break
|
|
||||||
if (obj.getInt("errorCode") != 429) throw UnexpectedError(mac, response)
|
|
||||||
work.conn = url.openConnection() as HttpURLConnection
|
|
||||||
delay(max(1, work.conn.getHeaderField("Retry-After")?.toLongOrNull().let {
|
|
||||||
if (it == null) {
|
|
||||||
Timber.w(UnexpectedError(mac,
|
|
||||||
work.conn.headerFields.entries.joinToString { (k, v) -> "$k: $v" }))
|
|
||||||
1
|
|
||||||
} else it
|
|
||||||
}) * 1000)
|
|
||||||
}
|
|
||||||
if (!obj.getBoolean("found")) {
|
|
||||||
// no vendor found, we should not retry in the future
|
|
||||||
AppDatabase.instance.clientRecordDao.upsert(mac) { macLookupPending = false }
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
val country = obj.getString("country")
|
|
||||||
val company = obj.getString("company")
|
val company = obj.getString("company")
|
||||||
val result = if (country.length != 2) {
|
val match = extractCountry(mac, response, obj)
|
||||||
Timber.w(UnexpectedError(mac, response))
|
val result = if (match != null) {
|
||||||
company
|
String(match.groupValues[1].flatMap { listOf('\uD83C', it + 0xDDA5) }.toCharArray()) + ' ' + company
|
||||||
} else String(country.flatMap { listOf('\uD83C', it + 0xDDA5) }.toCharArray()) + ' ' + company
|
} else company
|
||||||
AppDatabase.instance.clientRecordDao.upsert(mac) {
|
AppDatabase.instance.clientRecordDao.upsert(mac) {
|
||||||
nickname = result
|
nickname = result
|
||||||
macLookupPending = false
|
macLookupPending = false
|
||||||
}
|
}
|
||||||
} catch (e: JSONException) {
|
} catch (e: JSONException) {
|
||||||
Timber.w(e)
|
if ((e as? UnexpectedError)?.error == "no result") {
|
||||||
|
// no vendor found, we should not retry in the future
|
||||||
|
AppDatabase.instance.clientRecordDao.upsert(mac) { macLookupPending = false }
|
||||||
|
} else Timber.w(e)
|
||||||
if (explicit) SmartSnackbar.make(e).show()
|
if (explicit) SmartSnackbar.make(e).show()
|
||||||
} catch (e: IOException) {
|
} catch (e: Throwable) {
|
||||||
Timber.d(e)
|
Timber.d(e)
|
||||||
if (explicit) SmartSnackbar.make(e).show()
|
if (explicit) SmartSnackbar.make(e).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
macLookupBusy[mac] = work
|
}
|
||||||
|
|
||||||
|
private fun extractCountry(mac: MacAddressCompat, response: String, obj: JSONObject): MatchResult? {
|
||||||
|
countryCodeRegex.matchEntire(obj.optString("country"))?.also { return it }
|
||||||
|
val address = obj.optString("address")
|
||||||
|
if (address.isBlank()) return null
|
||||||
|
countryCodeRegex.find(address)?.also { return it }
|
||||||
|
Timber.w(UnexpectedError(mac, response))
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,8 +86,7 @@ fun makeIpSpan(ip: InetAddress) = ip.hostAddress.let {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun makeMacSpan(mac: String) = if (app.hasTouch) SpannableString(mac).apply {
|
fun makeMacSpan(mac: String) = if (app.hasTouch) SpannableString(mac).apply {
|
||||||
setSpan(CustomTabsUrlSpan("https://maclookup.app/search/result?mac=" + mac.substring(0, 13)),
|
setSpan(CustomTabsUrlSpan("https://macvendors.co/results/$mac"), 0, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
0, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
} else mac
|
} else mac
|
||||||
|
|
||||||
fun NetworkInterface.formatAddresses(macOnly: Boolean = false) = SpannableStringBuilder().apply {
|
fun NetworkInterface.formatAddresses(macOnly: Boolean = false) = SpannableStringBuilder().apply {
|
||||||
|
|||||||
Reference in New Issue
Block a user