diff --git a/app/src/main/java/org/kiwix/kiwixmobile/webserver/WebServerHelper.kt b/app/src/main/java/org/kiwix/kiwixmobile/webserver/WebServerHelper.kt index 34086f027..58fedcc66 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/webserver/WebServerHelper.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/webserver/WebServerHelper.kt @@ -17,9 +17,16 @@ */ package org.kiwix.kiwixmobile.webserver -import io.reactivex.Flowable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.timeout +import kotlinx.coroutines.launch import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.utils.DEFAULT_PORT import org.kiwix.kiwixmobile.core.utils.ServerUtils @@ -29,21 +36,24 @@ import org.kiwix.kiwixmobile.core.utils.ServerUtils.getIpAddress import org.kiwix.kiwixmobile.core.utils.files.Log import org.kiwix.kiwixmobile.webserver.wifi_hotspot.IpAddressCallbacks import org.kiwix.kiwixmobile.webserver.wifi_hotspot.ServerStatus -import java.util.concurrent.TimeUnit import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds /** * WebServerHelper class is used to set up the suitable environment i.e. getting the * ip address and port no. before starting the WebServer * Created by Adeel Zafar on 18/07/2019. */ + +const val FINDING_IP_ADDRESS_TIMEOUT = 15_000L +const val FINDING_IP_ADDRESS_RETRY_TIME = 1_000L + class WebServerHelper @Inject constructor( private val kiwixServerFactory: KiwixServer.Factory, private val ipAddressCallbacks: IpAddressCallbacks ) { private var kiwixServer: KiwixServer? = null private var isServerStarted = false - private var validIpAddressDisposable: Disposable? = null suspend fun startServerHelper( selectedBooksPath: ArrayList, @@ -98,31 +108,53 @@ class WebServerHelper @Inject constructor( ServerUtils.isServerStarted = isStarted } - // Keeps checking if hotspot has been turned using the ip address with an interval of 1 sec - // If no ip is found after 15 seconds, dismisses the progress dialog - @Suppress("MagicNumber") - fun pollForValidIpAddress() { - validIpAddressDisposable = - Flowable.interval(1, TimeUnit.SECONDS) - .map { getIp() } - .filter { s: String? -> s != INVALID_IP } - .timeout(15, TimeUnit.SECONDS) - .take(1) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { s: String? -> - ipAddressCallbacks.onIpAddressValid() - Log.d(TAG, "onSuccess: $s") - } - ) { e: Throwable? -> - Log.d(TAG, "Unable to turn on server", e) + /** + * Starts polling for a valid IP address using a [Flow]. + * - Polls every [FINDING_IP_ADDRESS_RETRY_TIME] milliseconds. + * - If a valid IP is found, invokes [IpAddressCallbacks.onIpAddressInvalid]. + * - If no valid IP is found within [FINDING_IP_ADDRESS_TIMEOUT] seconds, + * invokes [IpAddressCallbacks.onIpAddressInvalid]. + * - The flow runs on [Dispatchers.IO] and results are collected on the Main thread. + * - Automatically cancels if [serviceScope] is cancelled (e.g. lifecycle aware). + */ + @OptIn(FlowPreview::class) + @Suppress("InjectDispatcher") + fun pollForValidIpAddress(serviceScope: CoroutineScope) { + serviceScope.launch(Dispatchers.Main) { + ipPollingFlow() + .timeout(FINDING_IP_ADDRESS_TIMEOUT.seconds) + .catch { + Log.d(TAG, "Unable to turn on server", it) ipAddressCallbacks.onIpAddressInvalid() + }.collect { + ipAddressCallbacks.onIpAddressValid() + Log.d(TAG, "onSuccess: $it") } + } } - fun dispose() { - validIpAddressDisposable?.dispose() - } + /** + * Creates a [Flow] that emits the current IP address every [FINDING_IP_ADDRESS_RETRY_TIME] milliseconds. + * - If the returned IP is not [INVALID_IP], the flow completes. + * - The flow runs entirely on [Dispatchers.IO]. + */ + @Suppress("InjectDispatcher", "TooGenericExceptionCaught") + private fun ipPollingFlow(): Flow = flow { + while (true) { + // if ip address is not found wait for 1 second to again getting the ip address. + // this is equivalent to our `rxJava` code. + delay(FINDING_IP_ADDRESS_RETRY_TIME) + val ip = try { + getIp() + } catch (e: Exception) { + Log.e(TAG, "Error getting IP address", e) + INVALID_IP + } + emit(ip) + + if (ip != INVALID_IP) break + } + }.flowOn(Dispatchers.IO) companion object { private const val TAG = "WebServerHelper" diff --git a/app/src/main/java/org/kiwix/kiwixmobile/webserver/wifi_hotspot/HotspotService.kt b/app/src/main/java/org/kiwix/kiwixmobile/webserver/wifi_hotspot/HotspotService.kt index 68014cb46..677bf145e 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/webserver/wifi_hotspot/HotspotService.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/webserver/wifi_hotspot/HotspotService.kt @@ -24,6 +24,8 @@ import android.os.IBinder import android.widget.Toast import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.kiwix.kiwixmobile.KiwixApp @@ -53,6 +55,7 @@ class HotspotService : @set:Inject var hotspotStateReceiver: HotspotStateReceiver? = null + private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) private var zimHostCallbacks: ZimHostCallbacks? = null private val serviceBinder: IBinder = HotspotBinder(this) @@ -64,13 +67,13 @@ class HotspotService : .build() .inject(this) super.onCreate() - hotspotStateReceiver?.let(this::registerReceiver) + hotspotStateReceiver?.let(::registerReceiver) } override fun onDestroy() { - webServerHelper?.dispose() - hotspotStateReceiver?.let(this@HotspotService::unregisterReceiver) + hotspotStateReceiver?.let(::unregisterReceiver) super.onDestroy() + serviceScope.cancel() } @Suppress("NestedBlockDepth", "InjectDispatcher") @@ -79,7 +82,7 @@ class HotspotService : ACTION_START_SERVER -> { val restartServer = intent.getBooleanExtra(RESTART_SERVER, false) intent.getStringArrayListExtra(ZimHostFragment.SELECTED_ZIM_PATHS_KEY)?.let { - CoroutineScope(Dispatchers.Main).launch { + serviceScope.launch { val serverStatus = withContext(Dispatchers.IO) { webServerHelper?.startServerHelper(it, restartServer) @@ -109,7 +112,7 @@ class HotspotService : stopHotspotAndDismissNotification() } - ACTION_CHECK_IP_ADDRESS -> webServerHelper?.pollForValidIpAddress() + ACTION_CHECK_IP_ADDRESS -> webServerHelper?.pollForValidIpAddress(serviceScope) else -> {} } return START_NOT_STICKY