fix: stored components can be deleted after a Proton update

This commit is contained in:
maxjivi05 committed 2026-10-23 18:28:35 -04:00
1 parent 906e0d1f7d
commit 94b530ecb2
2 files changed
+67 -17

No files matched your search

@@ -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<StoredPackage>, val originals: List<StoredOriginal>)
private fun packagesInUse(state: JSONObject): Set<String> {
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<String, Proton>): Set<String> {
val names = HashSet<String>()
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)
}
@@ -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))
}
}