Stores: GOG rows show 0 B, their disk size and a counted Install stage

A size of zero read as nothing ("Downloading 0% ·  / 1.1 GB"); it reads
0 B now, and small sizes in bytes. GOG and Amazon report the size on disk
from their manifests, shown beside the location as Epic's is. GOG writes
files in place while it downloads, so its Install stage has no file move
to count; it and Amazon's now count the finishing steps (the record and
launcher, the art, Steam) instead of sitting empty. GOG's plan line says
the window the tier gives the engine (Max = 96).
This commit is contained in:
The412Banner committed 2026-10-08 22:16:17 -04:00
1 parent 4748a217b3
commit e835f5c523
7 files changed
+29 -5

No files matched your search

@@ -86,7 +86,8 @@ fun cleanStoreText(raw: String?): String {
fun formatBytes(bytes: Long): String = when {
bytes >= 1_073_741_824L -> "%.1f GB".format(bytes / 1_073_741_824.0)
bytes >= 1_048_576L -> "%.1f MB".format(bytes / 1_048_576.0)
bytes > 0L -> "%.0f KB".format(bytes / 1024.0)
bytes >= 1024L -> "%.0f KB".format(bytes / 1024.0)
bytes >= 0L -> "$bytes B"
else -> ""
}
@@ -39,16 +39,23 @@ object StoreInstalls {
* throw, so a failure there fails the download with its message; the art and the Steam side
* are best-effort and only logged.
*/
fun complete(context: Context, folder: File, sidecar: StoreGameSidecar) {
fun complete(context: Context, folder: File, sidecar: StoreGameSidecar, onStep: ((Int, Int) -> Unit)? = null) {
val app = context.applicationContext
onStep?.invoke(0, STEPS)
val finished = StoreLaunch.writeLauncher(folder, sidecar.copy(state = StoreGameSidecar.STATE_INSTALLED))
finished.write(folder)
if (StoreGameSidecar.read(folder)?.isInstalled != true) throw java.io.IOException("the install record could not be written in ${folder.name}")
onStep?.invoke(1, STEPS)
// The art before the listing, so the listing already carries it for the client's grid.
try { StoreArt.fetchInto(folder, finished) } catch (e: Exception) { Log.w(TAG, "art for ${folder.name}: ${e.message}") }
onStep?.invoke(2, STEPS)
register(app)
onStep?.invoke(STEPS, STEPS)
}
/** The finishing steps [complete] reports: the record and launcher, the art, Steam. */
private const val STEPS = 3
/** Rewrites the session's listing from a fresh scan and, with a client running, adds live. */
fun register(context: Context) {
val app = context.applicationContext
@@ -105,6 +105,7 @@ object AmazonBackend : StoreBackend {
}
override fun onBytes(done: Long, total: Long, speedBps: Long) { handle.progress(done, total, null, speedBps) }
override fun onLog(line: String) { handle.log(line) }
override fun onSizes(downloadBytes: Long, diskBytes: Long) { handle.diskSize(diskBytes) }
}) ?: return null
if (cancelled.get()) return null
handle.stage(DownloadStage.INSTALL, "Registering with Steam…")
@@ -121,7 +122,7 @@ object AmazonBackend : StoreBackend {
cover = item.tallImageUrl ?: item.imageUrl, hero = item.imageUrl,
extra = mapOf("entitlementId" to game.entitlementId, "sku" to game.productSku),
)
StoreInstalls.complete(app, folder, sidecar)
StoreInstalls.complete(app, folder, onStep = { done, steps -> handle.stageProgress(done.toLong(), steps.toLong()) }, sidecar = sidecar)
return folder.path
}
@@ -46,6 +46,8 @@ public final class AmazonDownloadManager {
void onProgress(String message, int pct);
default void onBytes(long done, long total, long speedBps) {}
default void onLog(String line) {}
/** What this run fetches and what the game takes on disk. */
default void onSizes(long downloadBytes, long diskBytes) {}
}
public static final class Result {
@@ -78,6 +80,7 @@ public final class AmazonDownloadManager {
long usable = installDir.getUsableSpace();
if (manifest.totalInstallSize > 0 && usable > 0 && manifest.totalInstallSize > usable)
throw new InstallException("Not enough free space: need " + fmt(manifest.totalInstallSize) + ", only " + fmt(usable) + " free");
cb.onSizes(manifest.totalInstallSize, manifest.totalInstallSize);
cb.onBytes(0, manifest.totalInstallSize, 0);
if (cancel.get()) return null;
@@ -95,6 +95,7 @@ object GogBackend : StoreBackend {
}
override fun onBytes(done: Long, total: Long, speedBps: Long) { handle.progress(done, total, null, speedBps) }
override fun onLog(line: String) { handle.log(line) }
override fun onSizes(downloadBytes: Long, diskBytes: Long) { handle.diskSize(diskBytes) }
override fun onStage(stage: String, done: Long, total: Long, items: Int, itemsTotal: Int) {
handle.stage(when (stage) { "check" -> DownloadStage.MANIFEST; "verify" -> DownloadStage.VERIFY; else -> DownloadStage.INSTALL })
handle.stageProgress(done, total, items, itemsTotal, bytes = stage == "install")
@@ -108,7 +109,7 @@ object GogBackend : StoreBackend {
cover = item.tallImageUrl ?: item.imageUrl, hero = item.imageUrl,
)
if (sidecar.exe.isEmpty()) handle.log("gog: no exe found in ${folder.name}; pick one in Steam settings › Added games")
StoreInstalls.complete(app, folder, if (sidecar.exe.isEmpty()) sidecar.copy(exe = "game.exe") else sidecar)
StoreInstalls.complete(app, folder, onStep = { done, steps -> handle.stageProgress(done.toLong(), steps.toLong()) }, sidecar = if (sidecar.exe.isEmpty()) sidecar.copy(exe = "game.exe") else sidecar)
return folder.path
}
@@ -62,6 +62,8 @@ public final class GogDownloadManager {
default void onBytes(long done, long total, long speedBps) {}
/** A diagnostic line for the engine log. */
default void onLog(String line) {}
/** What this run fetches and what the game takes on disk. */
default void onSizes(long downloadBytes, long diskBytes) {}
/**
* The active stage's own count: {@code stage} is "verify" or "install", {@code done} of
* {@code total} an amount (bytes, or items when there are no bytes), {@code items} of
@@ -203,6 +205,7 @@ public final class GogDownloadManager {
final AtomicReference<String> cdnBaseRef = new AtomicReference<>(cdnBase);
final AtomicInteger cdnRefreshCount = new AtomicInteger(0);
final int MAX_CDN_REFRESH = 5;
cb.onSizes(planned, planned);
cb.onBytes(0, planned, 0);
cb.onLog("gog: " + total + " files, " + formatBytes(planned) + ", base=" + baseProductId);
@@ -353,6 +356,7 @@ public final class GogDownloadManager {
final AtomicLong lastSpeedB = new AtomicLong(0);
final AtomicLong speedBps = new AtomicLong(0);
final AtomicBoolean anyFailed = new AtomicBoolean(false);
cb.onSizes(planned, planned);
cb.onBytes(0, planned, 0);
int threads = downloadThreads();
if (GogNative.isAvailable()) {
@@ -527,7 +531,8 @@ public final class GogDownloadManager {
StoreDownloadTier tier = StoreDownloadTier.Companion.current(ctx);
int workers = Math.max(1, Math.min(128, tier.getNetworkWindow()));
int process = Math.max(16, Math.max(processWorkers, tier.getProcessWorkers()));
cb.onLog("gog: engine=native workers=" + workers + " process_workers=" + process + " tier=" + tier.getId());
// The tier's window goes to the engine as its ceiling (Max = 96); the per-host spread is the engine's.
cb.onLog("gog: plan kind=" + kind + " files=" + total + " bytes=" + planned + " window=" + workers + " process_workers=" + process + " tier=" + tier.getId() + " engine=native");
final String[] manifests = depotJsons.toArray(new String[0]);
final java.util.Set<String> donePaths = java.util.concurrent.ConcurrentHashMap.newKeySet();
int run = 0;
+6
View File
@@ -144,6 +144,12 @@ without it and Setup says "Stores engine not built").
(`token`, `__token__`, `f_token`, `hdnts`, access/refresh/id tokens, `code=`) and Authorization /
Cookie headers are blanked outside URLs too. `StoreLogTest` covers a GOG secure link and an Epic
Akamai URL. The engine's own logcat lines (tag `EpicNative` etc.) are written on the native side.
- **GOG rows (device, DOOM + DOOM II):** bytes read "0 B" instead of blank; GOG and Amazon report
their size on disk from the manifest; their Install stage counts its finishing steps (record and
launcher, art, Steam) instead of sitting empty - GOG writes files in place while downloading, so
there is no file move to count. A stage a store skips reads complete once a later one starts.
The tier's window does reach GOG's engine (Max = 96 as its ceiling); the plan line now says so
(`gog: plan … window=96 … tier=max`). The per-host spread is the engine's and was not changed.
- **Stages named for what they do; both sizes; Cancel deletes.** Epic's check of the files already
there is part of Manifest ("Checking 120/515"), never a 100% Verify before anything is fetched.
Install has its own write rate and ETA from the bytes the assembler writes, and files n/N. The