diff --git a/app/src/main/java/com/droiddeck/launcher/session/ComponentsManager.kt b/app/src/main/java/com/droiddeck/launcher/session/ComponentsManager.kt index d1aae9d72..d05ba2836 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/ComponentsManager.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/ComponentsManager.kt @@ -721,13 +721,19 @@ object ComponentsManager { class StoredOriginal(val protonId: String, val protonName: String?, val comp: String, val protonVersion: String, val size: Long, val kept: Boolean) class Stored(val packages: List, val originals: List) - private fun packagesInUse(state: JSONObject): Set { + internal fun liveSelection(key: String, entry: JSONObject, proton: Proton?): Boolean = + proton != null && (key == "queued" || entry.optString("protonVersion") == proton.version) + + private fun packagesInUse(state: JSONObject, installed: Map): Set { val names = HashSet() for (key in listOf("active", "queued")) { val byProton = state.optJSONObject(key) ?: continue for (pid in byProton.keys()) { val comps = byProton.optJSONObject(pid) ?: continue - for (comp in comps.keys()) comps.optJSONObject(comp)?.optString("file")?.takeIf { it.isNotEmpty() }?.let(names::add) + for (comp in comps.keys()) { + val entry = comps.optJSONObject(comp) ?: continue + if (liveSelection(key, entry, installed[pid])) entry.optString("file").takeIf { it.isNotEmpty() }?.let(names::add) + } } } val profiles = state.optJSONObject("profiles") @@ -747,8 +753,8 @@ object ComponentsManager { fun stored(context: Context): Stored = synchronized(lock) { val state = loadState(context) - val used = packagesInUse(state) val installed = protons(context).associateBy { it.id } + val used = packagesInUse(state, installed) val originals = originalsDir(context).listFiles()?.filter { it.isDirectory }?.flatMap { proton -> proton.listFiles()?.filter { it.isDirectory }?.flatMap { version -> COMPONENTS.mapNotNull { comp -> @@ -777,23 +783,35 @@ object ComponentsManager { fun deletePackage(context: Context, file: String): String = synchronized(lock) { val name = safeName(file) val state = loadState(context) - val queued = state.sub("queued") - for (pid in queued.keys()) { - val comps = queued.optJSONObject(pid) ?: continue - for (comp in comps.keys()) check(comps.optJSONObject(comp)?.optString("file") != name) { context.getString(R.string.cmgr_package_in_use, name, pid) } - } - val active = state.sub("active") - for (pid in active.keys()) { - val comps = active.optJSONObject(pid) ?: continue - for (comp in comps.keys()) check(comps.optJSONObject(comp)?.optString("file") != name) { context.getString(R.string.cmgr_package_in_use, name, pid) } - } - val profiles = state.sub("profiles") - for (scope in profiles.keys()) { - val profile = profiles.optJSONObject(scope) ?: continue - check(COMPONENTS.none { profile.optString(it) == name }) { context.getString(R.string.cmgr_package_in_use, name, scope) } + val installed = protons(context).associateBy { it.id } + var changed = false + for (key in listOf("queued", "active")) { + val byProton = state.sub(key) + for (pid in byProton.keys().asSequence().toList()) { + val comps = byProton.optJSONObject(pid) ?: continue + for (comp in comps.keys().asSequence().toList()) { + val entry = comps.optJSONObject(comp) ?: continue + if (entry.optString("file") != name) continue + check(!liveSelection(key, entry, installed[pid])) { context.getString(R.string.cmgr_package_in_use, name, installed[pid]?.name ?: pid) } + comps.remove(comp) + changed = true + } + if (comps.length() == 0) byProton.remove(pid) + } } val f = File(packagesDir(context), name) check(f.isFile) { context.getString(R.string.cmgr_package_not_stored, name) } + val profiles = state.sub("profiles") + for (scope in profiles.keys().asSequence().toList()) { + val profile = profiles.optJSONObject(scope) ?: continue + for (comp in COMPONENTS.filter { profile.optString(it) == name }) { + rememberProfiledComponent(state, comp) + profile.remove(comp) + changed = true + } + if (profile.length() == 0) profiles.remove(scope) + } + if (changed) saveState(context, state) f.delete() context.getString(R.string.cmgr_deleted, name) } diff --git a/app/src/test/java/com/droiddeck/launcher/session/ComponentsSelectionTest.kt b/app/src/test/java/com/droiddeck/launcher/session/ComponentsSelectionTest.kt new file mode 100644 index 000000000..517d6ad2b --- /dev/null +++ b/app/src/test/java/com/droiddeck/launcher/session/ComponentsSelectionTest.kt @@ -0,0 +1,32 @@ +package com.droiddeck.launcher.session + +import org.json.JSONObject +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import java.io.File + +@RunWith(RobolectricTestRunner::class) +class ComponentsSelectionTest { + private val proton = ComponentsManager.Proton("DroidDeck-Proton_11-11.1", "DroidDeck-Proton 11-11.1", File("/p"), "/p", "DroidDeck-Proton-11-11.1", false) + private val entry = JSONObject().put("file", "dxvk-low-latency-arm64ec-3.1.1-2-linux.wcp").put("protonVersion", "DroidDeck-Proton-11-11.1") + + @Test + fun `a package swapped into an installed Proton is in use`() { + assertTrue(ComponentsManager.liveSelection("active", entry, proton)) + assertTrue(ComponentsManager.liveSelection("queued", JSONObject().put("file", "x.wcp"), proton)) + } + + @Test + fun `a package left on a removed Proton is not in use`() { + assertFalse(ComponentsManager.liveSelection("active", entry, null)) + assertFalse(ComponentsManager.liveSelection("queued", entry, null)) + } + + @Test + fun `a package from before a Proton update is not in use`() { + assertFalse(ComponentsManager.liveSelection("active", JSONObject(entry.toString()).put("protonVersion", "DroidDeck-Proton-11-9.1"), proton)) + } +}