mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
feat(network): enhance connectivity monitoring with request reporting
This commit is contained in:
@@ -7,28 +7,54 @@ import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import hu.bbara.purefin.core.data.NetworkMonitor
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import java.net.ConnectException
|
||||
import java.net.NoRouteToHostException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.UnknownHostException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ConnectivityNetworkMonitor @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
@param:ApplicationContext private val context: Context,
|
||||
) : NetworkMonitor {
|
||||
|
||||
override val isOnline: Flow<Boolean> = callbackFlow {
|
||||
val connectivityManager = context.getSystemService(ConnectivityManager::class.java)
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
private val connectivityManager = context.getSystemService(ConnectivityManager::class.java)
|
||||
private val isServerReachable = MutableStateFlow(true)
|
||||
|
||||
private val isDeviceOnline: Flow<Boolean> = callbackFlow {
|
||||
var wasDeviceOnline = connectivityManager.isCurrentlyConnected()
|
||||
|
||||
val callback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
trySend(true)
|
||||
override fun onCapabilitiesChanged(
|
||||
network: Network,
|
||||
networkCapabilities: NetworkCapabilities
|
||||
) {
|
||||
val isCurrentlyConnected = connectivityManager.isCurrentlyConnected()
|
||||
if (isCurrentlyConnected && !wasDeviceOnline) {
|
||||
isServerReachable.value = true
|
||||
}
|
||||
wasDeviceOnline = isCurrentlyConnected
|
||||
trySend(isCurrentlyConnected)
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
trySend(connectivityManager.isCurrentlyConnected())
|
||||
val isCurrentlyConnected = connectivityManager.isCurrentlyConnected()
|
||||
wasDeviceOnline = isCurrentlyConnected
|
||||
trySend(isCurrentlyConnected)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,14 +62,51 @@ class ConnectivityNetworkMonitor @Inject constructor(
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.build()
|
||||
|
||||
trySend(connectivityManager.isCurrentlyConnected())
|
||||
trySend(wasDeviceOnline)
|
||||
connectivityManager.registerNetworkCallback(request, callback)
|
||||
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
|
||||
}.distinctUntilChanged()
|
||||
|
||||
override val isOnline: StateFlow<Boolean> = combine(
|
||||
isDeviceOnline,
|
||||
isServerReachable
|
||||
) { isDeviceOnline, isServerReachable ->
|
||||
isDeviceOnline && isServerReachable
|
||||
}.distinctUntilChanged()
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = connectivityManager.isCurrentlyConnected() && isServerReachable.value
|
||||
)
|
||||
|
||||
override fun reportRequestSucceeded() {
|
||||
isServerReachable.value = true
|
||||
}
|
||||
|
||||
override fun reportRequestFailed(error: Throwable) {
|
||||
if (error.isConnectivityFailure()) {
|
||||
isServerReachable.value = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun ConnectivityManager.isCurrentlyConnected(): Boolean {
|
||||
val network = activeNetwork ?: return false
|
||||
val caps = getNetworkCapabilities(network) ?: return false
|
||||
return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
return caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
|
||||
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||
}
|
||||
|
||||
private fun Throwable.isConnectivityFailure(): Boolean {
|
||||
var current: Throwable? = this
|
||||
while (current != null) {
|
||||
when (current) {
|
||||
is UnknownHostException,
|
||||
is ConnectException,
|
||||
is NoRouteToHostException,
|
||||
is SocketTimeoutException -> return true
|
||||
}
|
||||
current = current.cause
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import android.os.SystemClock
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import hu.bbara.purefin.core.data.JellyfinServerCandidate
|
||||
import hu.bbara.purefin.core.data.NetworkMonitor
|
||||
import hu.bbara.purefin.core.data.PlaybackMethod
|
||||
import hu.bbara.purefin.core.data.PlaybackReportContext
|
||||
import hu.bbara.purefin.core.data.QuickConnectSession
|
||||
@@ -64,8 +65,9 @@ import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class JellyfinApiClient @Inject constructor(
|
||||
@ApplicationContext private val applicationContext: Context,
|
||||
@param:ApplicationContext private val applicationContext: Context,
|
||||
private val userSessionRepository: UserSessionRepository,
|
||||
private val networkMonitor: NetworkMonitor,
|
||||
) {
|
||||
private val jellyfin = createJellyfin {
|
||||
context = applicationContext
|
||||
@@ -583,12 +585,14 @@ class JellyfinApiClient @Inject constructor(
|
||||
val startedAt = SystemClock.elapsedRealtime()
|
||||
return try {
|
||||
val result = block()
|
||||
networkMonitor.reportRequestSucceeded()
|
||||
val elapsedMs = SystemClock.elapsedRealtime() - startedAt
|
||||
Timber.tag(TAG).d("$functionName took ${elapsedMs}ms")
|
||||
result
|
||||
} catch (error: CancellationException) {
|
||||
throw error
|
||||
} catch (error: Exception) {
|
||||
networkMonitor.reportRequestFailed(error)
|
||||
val elapsedMs = SystemClock.elapsedRealtime() - startedAt
|
||||
Timber.tag(TAG).e(error, "$functionName failed after ${elapsedMs}ms")
|
||||
throw error
|
||||
|
||||
Reference in New Issue
Block a user