Use known game size for preflight checks

This commit is contained in:
Juan Marti Mercado committed 2026-10-13 19:15:32 -04:00
1 parent 82b8cf1230
commit eaa095925c
5 files changed
+13 -58

No files matched your search

@@ -33,9 +33,6 @@ object StoreInstallRoot {
val enough: Boolean,
val requiredBytes: Long,
val availableBytes: Long,
val internalRequiredBytes: Long = 0L,
val internalAvailableBytes: Long = 0L,
val isInternalShortage: Boolean = false,
val errorMessage: String? = null
)
@@ -48,58 +45,21 @@ object StoreInstallRoot {
}
/**
* Estimated bytes needed for an install. Epic on internal storage needs both chunk cache
* and assembled files simultaneously (~2x game size).
* The catalog size is the known minimum for an install. Epic's actual chunk-cache requirement
* is determined from its manifest before chunk downloads begin.
*/
fun estimatedBytesNeeded(item: CatalogItem, targetRoot: File, isInternal: Boolean): Long {
val size = StoreSizes.size(item)
if (size <= 0L) return 0L
return if (item.store == Store.EPIC && isInternal) size * 2 else size
}
fun estimatedBytesNeeded(item: CatalogItem): Long = StoreSizes.size(item).coerceAtLeast(0L)
/** Pre-install storage check to detect insufficient space before starting. */
fun checkStorage(context: Context, item: CatalogItem, targetRoot: File): StorageCheck {
val size = StoreSizes.size(item)
val size = estimatedBytesNeeded(item)
val freeTarget = availableBytes(targetRoot)
if (size <= 0L) return StorageCheck(enough = true, requiredBytes = 0L, availableBytes = freeTarget)
val internal = isInternal(context, targetRoot)
if (internal) {
val needed = if (item.store == Store.EPIC) size * 2 else size
if (freeTarget in 1 until needed) {
val msg = if (item.store == Store.EPIC) {
context.getString(
com.droiddeck.launcher.R.string.stores_storage_insufficient,
formatBytes(needed),
formatBytes(freeTarget)
) + " (" + formatBytes(size) + " + " + formatBytes(size) + " " + context.getString(com.droiddeck.launcher.R.string.stores_stage_download).lowercase() + ")"
} else {
context.getString(com.droiddeck.launcher.R.string.stores_storage_insufficient, formatBytes(needed), formatBytes(freeTarget))
}
return StorageCheck(enough = false, requiredBytes = needed, availableBytes = freeTarget, errorMessage = msg)
}
return StorageCheck(enough = true, requiredBytes = needed, availableBytes = freeTarget)
} else {
if (freeTarget in 1 until size) {
val msg = context.getString(com.droiddeck.launcher.R.string.stores_storage_insufficient, formatBytes(size), formatBytes(freeTarget))
return StorageCheck(enough = false, requiredBytes = size, availableBytes = freeTarget, errorMessage = msg)
}
if (item.store == Store.EPIC) {
val internalFree = availableBytes(context.cacheDir)
if (internalFree in 1 until size) {
val msg = context.getString(com.droiddeck.launcher.R.string.stores_storage_internal_cache_insufficient, formatBytes(size), formatBytes(internalFree))
return StorageCheck(
enough = false,
requiredBytes = size,
availableBytes = freeTarget,
internalRequiredBytes = size,
internalAvailableBytes = internalFree,
isInternalShortage = true,
errorMessage = msg
)
}
}
return StorageCheck(enough = true, requiredBytes = size, availableBytes = freeTarget)
if (freeTarget in 1 until size) {
val msg = context.getString(com.droiddeck.launcher.R.string.stores_storage_insufficient, formatBytes(size), formatBytes(freeTarget))
return StorageCheck(enough = false, requiredBytes = size, availableBytes = freeTarget, errorMessage = msg)
}
return StorageCheck(enough = true, requiredBytes = size, availableBytes = freeTarget)
}
/** The internal root: always there, always the default. */
@@ -72,7 +72,7 @@ internal fun InstallWhereDialog(item: CatalogItem, onDismiss: () -> Unit) {
Rise(1) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) {
targets.forEachIndexed { i, t ->
val needed = com.droiddeck.launcher.stores.StoreInstallRoot.estimatedBytesNeeded(item, t.root, !t.removable)
val needed = com.droiddeck.launcher.stores.StoreInstallRoot.estimatedBytesNeeded(item)
val isLow = needed > 0 && t.freeBytes in 1 until needed
val freeText = if (isLow) {
stringResource(R.string.stores_storage_low, formatBytes(t.freeBytes), formatBytes(needed))
@@ -511,7 +511,7 @@ private fun InstallWhere(item: CatalogItem) {
StepTitle(title)
StepList {
targets.forEachIndexed { i, t ->
val needed = com.droiddeck.launcher.stores.StoreInstallRoot.estimatedBytesNeeded(item, t.root, !t.removable)
val needed = com.droiddeck.launcher.stores.StoreInstallRoot.estimatedBytesNeeded(item)
val isLow = needed > 0 && t.freeBytes in 1 until needed
val trailingText = if (isLow) {
stringResource(R.string.stores_storage_low, formatBytes(t.freeBytes), formatBytes(needed))
-1
View File
@@ -2258,7 +2258,6 @@
<string name="stores_target_internal">Internal</string>
<string name="stores_target_free">%1$s free</string>
<string name="stores_storage_insufficient">Not enough free space: need %1$s, only %2$s free</string>
<string name="stores_storage_internal_cache_insufficient">Not enough internal space for download cache: need %1$s, only %2$s free</string>
<string name="stores_storage_low">Insufficient space (%1$s free, need %2$s)</string>
<string name="stores_notification_channel">Store downloads</string>
<string name="stores_notification_channel_hint">Shows game download progress and keeps downloads running in the background</string>
@@ -60,16 +60,12 @@ class StoreInstallRootTest {
assertEquals("a_b_c", dir.name)
}
@Test fun estimatedBytesNeededCountsDoubleForEpicInternalStorage() {
@Test fun estimatedBytesNeededUsesKnownInstallSizeForAllStores() {
val item = CatalogItem(Store.EPIC, "game1", "Game 1", imageUrl = null, owned = true, sizeBytes = 10_000_000L)
val internalDir = StoreInstallRoot.internalRoot(app)
val cardDir = File(app.filesDir, "card/Games")
assertEquals(20_000_000L, StoreInstallRoot.estimatedBytesNeeded(item, internalDir, isInternal = true))
assertEquals(10_000_000L, StoreInstallRoot.estimatedBytesNeeded(item, cardDir, isInternal = false))
assertEquals(10_000_000L, StoreInstallRoot.estimatedBytesNeeded(item))
val gogItem = CatalogItem(Store.GOG, "game2", "Game 2", imageUrl = null, owned = true, sizeBytes = 10_000_000L)
assertEquals(10_000_000L, StoreInstallRoot.estimatedBytesNeeded(gogItem, internalDir, isInternal = true))
assertEquals(10_000_000L, StoreInstallRoot.estimatedBytesNeeded(gogItem))
}
@Test fun checkStorageReportsEnoughWhenSpaceIsUnknownOrAvailable() {