platypush-app/app/src/main/java/tech/platypush/app/Services.kt

126 lines
3.9 KiB
Kotlin

package tech.platypush.app
import android.content.ContentValues.TAG
import android.content.Context
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import android.util.Log
import java.util.*
import java.util.concurrent.locks.ReentrantLock
/**
* Service data class
*/
data class Service(val host: String, val port: Int, val name: String?) {
fun toMap(): Map<String, Any?> {
return mapOf(
"host" to host,
"port" to port,
"name" to name
)
}
}
/**
* ZeroConf/Bonjour/mDNS event listener
*/
class Listener(private val scanner: Scanner): NsdManager.DiscoveryListener {
val serviceType = "_platypush-http._tcp."
override fun onDiscoveryStarted(regType: String) {
Log.d(TAG, "Service discovery started")
}
override fun onServiceFound(service: NsdServiceInfo) {
Log.d(TAG, "Service discovery succeeded: $service")
if (service.serviceType != serviceType) {
Log.d(TAG, "Unknown service type: ${service.serviceType}")
return
}
scanner.resolveService(service)
}
override fun onServiceLost(service: NsdServiceInfo) {
Log.w(TAG, "Service lost: $service")
}
override fun onDiscoveryStopped(serviceType: String) {
Log.i(TAG, "Discovery stopped: $serviceType")
}
override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
Log.e(TAG, "Discovery start failed: Error code: $errorCode")
scanner.stopScan()
}
override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
Log.e(TAG, "Discovery stop failed: Error code: $errorCode")
scanner.stopScan()
}
}
/**
* ZeroConf/Bonjour/mDNS service scanner and resolver
*/
class Scanner(context: Context) {
private val nsdManager = context.getSystemService(Context.NSD_SERVICE) as NsdManager
private val listener = Listener(this)
private val services = HashMap<Pair<String, Int>, Service>()
private val resolverLock = ReentrantLock()
fun startScan() {
nsdManager.discoverServices(listener.serviceType, NsdManager.PROTOCOL_DNS_SD, listener)
}
fun stopScan() {
nsdManager.stopServiceDiscovery(listener)
}
fun getServices(): Collection<Service> {
return services.values
}
fun resolveService(service: NsdServiceInfo) {
// Service resolution is a critical section
resolverLock.lock()
val scanner = this
nsdManager.resolveService(service, object : NsdManager.ResolveListener {
override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
resolverLock.unlock()
val msg = "Resolve of service ${serviceInfo.serviceName} failed"
// Retry logic
when (errorCode) {
NsdManager.FAILURE_ALREADY_ACTIVE -> {
Thread.sleep(100)
scanner.resolveService(serviceInfo)
Log.w(TAG, "$msg: Resolver already active")
}
NsdManager.FAILURE_MAX_LIMIT -> {
Thread.sleep(5000)
scanner.resolveService(serviceInfo)
Log.e(TAG, "$msg: Maximum number of resolve requests reached")
}
NsdManager.FAILURE_INTERNAL_ERROR -> {
Log.e(TAG, "$msg: Internal error")
}
}
}
override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
services[Pair(serviceInfo.host.hostAddress, serviceInfo.port)] = Service(
serviceInfo.host.hostAddress, serviceInfo.port, serviceInfo.serviceName)
resolverLock.unlock()
Log.i(TAG, "Resolve succeeded: $serviceInfo")
}
})
}
}