Skip to content

Multi-threaded downloads & parallel SFTP OBB transfers with live speeds - #208

Closed
AbandonedAccount1111 wants to merge 6 commits into
nerdunit:masterfrom
xAstroBoy:feature/multithreaded-transfers
Closed

Multi-threaded downloads & parallel SFTP OBB transfers with live speeds#208
AbandonedAccount1111 wants to merge 6 commits into
nerdunit:masterfrom
xAstroBoy:feature/multithreaded-transfers

Conversation

@AbandonedAccount1111

@AbandonedAccount1111 AbandonedAccount1111 commented Jul 19, 2026

Copy link
Copy Markdown

Superseded by #209 (account migration).

SFTP fast transfers (rooted headsets running an SSH server):
- SFTP.cs: detects root (su -> uid=0), probes for an SSH server on the
  headset (ports 22/8022/2222), authenticates via key or password, and
  verifies the OBB dir is visible before use. Uploads OBBs over SFTP with
  the same progress/ETA UI as adb push, and transparently falls back to
  adb push when SFTP is unavailable or fails.
- Uses /storage/emulated/0/Android/obb (canonical) with /sdcard fallback.
- Titlebar shows Root and SFTP status after connect.
- SftpCredentialsForm: prompts for username + password and/or key file
  when a server is found but stored credentials do not authenticate.
- SFTP settings persisted in SettingsManager (host/port/user/pass/key).

ADB device detection:
- GetDevicesResilient() retries across an adb daemon restart so a
  version-mismatch server bounce (e.g. a different-version adb in PATH
  killing/restarting the server) no longer flashes "No Device Connected".

Measured on wireless (Quest 3, same link): SFTP ~50 MB/s vs adb push
~28 MB/s for a 200 MB payload, verified byte-identical (md5).
Downloads:
- ChunkedDownloader concurrency is now configurable via a new
  DownloadThreads setting (default 6) instead of a hardcoded cap of 4,
  and SingleThreadMode now defaults to off so multithreading is on by
  default. SingleThreadMode still forces a single stream when enabled.

SFTP OBB transfers:
- CopyObbCore now uploads multiple OBB files in parallel, one SFTP
  connection per worker (SSH.NET clients are not safe to share). Remote
  directories are pre-created once up front to avoid races, and files are
  distributed across workers greedily by size for balanced connections.

Transfer speed display:
- OBB copies now report live throughput (MB/s) in the status line for
  both the SFTP fast path and the adb push fallback, matching downloads.

Reliability:
- UpdateQuestInfoPanel ran its adb shell calls (getprop/df) on the UI
  thread; a slow or mid-restart adb daemon would freeze the whole window.
  Those calls now run off the UI thread.

Build:
- build.cmd's vswhere lookup adds -products * so it also finds MSBuild in
  a Visual Studio BuildTools install, not just full VS editions.
…s, non-hiding dialogs

ADB:
- RunAdbCommandToString now applies a safety timeout to shell commands so a
  wedged device command (e.g. Android's `df` blocking on an unresponsive mount)
  can no longer hang the app indefinitely. Large transfers and on-device
  backup/restore/install prompts are exempt so they are never force-killed.
  Timed reads use async stdout/stderr, which also removes a latent
  sequential-ReadToEnd deadlock. A timeoutMs override was added.

Startup:
- Stop hard-killing every adb.exe on launch. That tore down a running adb
  server and dropped any active wireless (adb connect) session, forcing a
  reconnect that often failed silently. start-server reuses a compatible
  running server, preserving the connected device.

SFTP OBB:
- After a transfer, best-effort chmod -R 0777 + restorecon on the OBB folder
  so the game can read files an sshd wrote as root (no-op on FUSE storage,
  needed where the sshd writes through to a real filesystem).

UI:
- Message boxes are now TopMost, shown in the taskbar, and brought to front on
  open, so a prompt can never hide behind another window/monitor and leave the
  disabled main window looking frozen while it waits for an answer.
- Add a FileZilla-style Transfer window (TransferWindow / TransferQueue): a
  non-modal popup that appears when a transfer starts and shows a row per file
  with a live progress bar, speed and status (Queued/Active/Done/Failed). It
  hides on close and re-shows on new activity, and has a "Clear finished"
  button. Both SFTP uploads and adb pushes feed into it.

- SFTP OBB upload is now a bounded work-stealing queue instead of firing every
  file at once: N workers (default 3, new SftpThreads setting) each hold one
  connection and pull the next file from a shared queue, so at most N files
  transfer at any moment regardless of OBB file count. Gentler on the on-device
  sshd/FUSE storage. SingleThreadMode still forces a single stream.
Transfer UI:
- Replace the popup transfer window with an integrated, collapsible strip that
  overlays the bottom of the games list only while files are moving, themed to
  match the list, and hides when idle. Successful rows auto-clear; failed rows
  stay with a right-click Retry / Remove / Clear finished menu.

Never hang (the softlock fix):
- Each SFTP upload now uses a 60s OperationTimeout, so a wedged channel throws
  instead of blocking UploadFile forever. A single stuck file could previously
  freeze an entire multi-hundred-file OBB upload; it can't anymore.

Auto-retry + verify:
- Each file is retried up to 3x on a fresh reconnection with backoff.
- After every upload the remote path is stat'd and checked for exact size; a
  file that isn't actually there (or is truncated) is treated as a failure and
  retried, never reported as done.

Failure isolation:
- One bad file no longer aborts the batch. Files that still fail after retries
  are left Failed in the strip with a per-file Retry action (SFTP.RetryUpload),
  instead of re-pushing the whole set. Only a total failure falls back to adb push.
AdvancedSharpAdbClient throws "An unknown error occurred." whenever it cannot
parse pm install's result, which frequently happens even when the install
actually succeeded (a connection blip right after install, unusual pm output).
Rookie treated that as a hard failure.

Now, on an install exception, VerifyInstalled reads the APK's package name and
version code with aapt, confirms the package is present on the device, and
requires the installed version code to match before reporting success. A real
failed upgrade (old version still present) still fails, so it is not a blind pass.
@xAstroBoy
xAstroBoy force-pushed the feature/multithreaded-transfers branch 2 times, most recently from 9a08ea6 to 08f5c69 Compare July 22, 2026 13:03
@xAstroBoy

Copy link
Copy Markdown

Superseded by #209 (account migration).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants