diff --git a/frontend/app/src/main/java/de/amosproj3/ziofa/bl/configuration/ConfigurationManager.kt b/frontend/app/src/main/java/de/amosproj3/ziofa/bl/configuration/ConfigurationManager.kt index 0d6f7d4a..b9d4d92f 100644 --- a/frontend/app/src/main/java/de/amosproj3/ziofa/bl/configuration/ConfigurationManager.kt +++ b/frontend/app/src/main/java/de/amosproj3/ziofa/bl/configuration/ConfigurationManager.kt @@ -35,6 +35,7 @@ import kotlinx.coroutines.launch * @param clientFactory the client factory for backend communication */ @OptIn(ExperimentalCoroutinesApi::class) +@Suppress("TooGenericException") // all exceptions should be forwarded for display class ConfigurationManager(clientFactory: ClientFactory) : FlowReduxStateMachine( initialState = ConfigurationState.Uninitialized(clientFactory = clientFactory) @@ -59,9 +60,13 @@ class ConfigurationManager(clientFactory: ClientFactory) : spec { inState { onEnter { state -> - val client = state.snapshot.clientFactory.connect() - val configuration = client.initializeConfiguration() - state.override { configuration.synchronizedOrErrorState(client) } + try { + val client = state.snapshot.clientFactory.connect() + val configuration = client.initializeConfiguration() + state.override { configuration.synchronizedOrErrorState(client) } + } catch (e: Exception) { + state.override { ConfigurationState.Error(e) } + } } } @@ -142,6 +147,10 @@ class ConfigurationManager(clientFactory: ClientFactory) : this.getConfiguration() } + /** + * Try to get the configuration and if it fails, obtain set an empty configuration + * (configuration has possibly never been written) + */ private suspend fun Client.initializeConfiguration() = try { Either.Right(this.getConfiguration()) diff --git a/frontend/app/src/main/java/de/amosproj3/ziofa/bl/processes/PackageInformationProvider.kt b/frontend/app/src/main/java/de/amosproj3/ziofa/bl/processes/PackageInformationProvider.kt index a57ca8f4..e925d692 100644 --- a/frontend/app/src/main/java/de/amosproj3/ziofa/bl/processes/PackageInformationProvider.kt +++ b/frontend/app/src/main/java/de/amosproj3/ziofa/bl/processes/PackageInformationProvider.kt @@ -9,6 +9,7 @@ import android.content.pm.PackageManager import android.graphics.drawable.Drawable import de.amosproj3.ziofa.api.processes.InstalledPackageInfo +/** Provides information about the installed packages on the system and caches this information. */ class PackageInformationProvider(private val packageManager: PackageManager) { private val installedPackagesCache: Map by lazy { diff --git a/frontend/app/src/main/java/de/amosproj3/ziofa/bl/processes/RunningComponentsProvider.kt b/frontend/app/src/main/java/de/amosproj3/ziofa/bl/processes/RunningComponentsProvider.kt index 984cb938..b4eb343f 100644 --- a/frontend/app/src/main/java/de/amosproj3/ziofa/bl/processes/RunningComponentsProvider.kt +++ b/frontend/app/src/main/java/de/amosproj3/ziofa/bl/processes/RunningComponentsProvider.kt @@ -21,6 +21,10 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import timber.log.Timber +/** + * Provides an updating list of running components, with components being apps (groups of processes) + * or standalone processes like native processes based on backend data and package info. + */ class RunningComponentsProvider( private val clientFactory: ClientFactory, private val packageInformationProvider: PackageInformationProvider, @@ -43,6 +47,10 @@ class RunningComponentsProvider( } } + /** + * Start polling the backend process list and update the [processesList] every + * [PROCESS_LIST_REFRESH_INTERVAL_MS] milliseconds. + */ private suspend fun startPollingProcessList() { while (true) { delay(PROCESS_LIST_REFRESH_INTERVAL_MS) @@ -51,11 +59,17 @@ class RunningComponentsProvider( } } + /** Group processes based on the [Process.cmd]. */ private fun Flow>.groupByProcessName() = this.map { processList -> processList.groupBy { process -> process.cmd.toReadableString() } } + /** + * Separate grouped processes into apps and standalone processes like native processes. All + * processes where [Process.cmd] is a package name will be treated as + * [RunningComponent.Application]. + */ private fun Flow>>.splitIntoAppsAndStandaloneProcesses() = this.map { packageProcessMap -> packageProcessMap.entries.map { (packageOrProcessName, processList) -> diff --git a/frontend/app/src/main/java/de/amosproj3/ziofa/ui/visualization/VisualizationViewModel.kt b/frontend/app/src/main/java/de/amosproj3/ziofa/ui/visualization/VisualizationViewModel.kt index 71b4e0ce..795428bd 100644 --- a/frontend/app/src/main/java/de/amosproj3/ziofa/ui/visualization/VisualizationViewModel.kt +++ b/frontend/app/src/main/java/de/amosproj3/ziofa/ui/visualization/VisualizationViewModel.kt @@ -21,6 +21,7 @@ import de.amosproj3.ziofa.ui.visualization.utils.getActiveMetricsForPids import de.amosproj3.ziofa.ui.visualization.utils.getPIDsOrNull import de.amosproj3.ziofa.ui.visualization.utils.isValidSelection import de.amosproj3.ziofa.ui.visualization.utils.toUIOptions +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -73,7 +74,15 @@ class VisualizationViewModel( selectedComponent = activeComponent, selectedMetric = activeMetric, selectedTimeframe = activeTimeframe, - componentOptions = configuredComponents.toUIOptions(), + componentOptions = + configuredComponents + .toUIOptions() + .plus( + activeComponent + ) // prevent the selected component from disappearing if process ends + .toSet() // convert to set to remove the duplicate if it is already in + // the list + .toImmutableList(), metricOptions = config.getActiveMetricsForPids(pids = activeComponent.getPIDsOrNull()), timeframeOptions = if (activeMetric != null) DEFAULT_TIMEFRAME_OPTIONS else null,