API reference

PakDownloadManager Class

Modifiers: final
Orchestrates the download-to-ready pipeline for pak files.
Responsibilities:
  • Pre-flight checks (disk space, existing file state)
  • Delegates actual download to the 3p's PakDownloader
  • Tracks progress and computes speed
  • Post-download validation (size, pluggable validator)
  • File cleanup for stale/corrupt downloads
  • Does NOT auto-mount — mounting is the 3p's decision
Thread safety: all public methods are safe to call from any thread. Internal state is managed via ConcurrentHashMap.
Example:
val manager = PakDownloadManager(myDownloader)
val request = DownloadRequest(
    uri = Uri.parse("https://cdn.example.com/content.pak"),
    destinationFile = File(context.filesDir, "content.pak"),
    pakName = "content",
    expectedSizeBytes = 52_428_800L,
)
val handle = manager.start(request)
handle.onStateChanged = { state ->
    when (state) {
        is DownloadState.Downloading -> updateProgressBar(state.progress)
        is DownloadState.Ready -> {
            val status = PakManager.mount("content", state.file)
            if (!status.isOk) handleMountError(status)
        }
        is DownloadState.Failed -> handleError(state.error)
        else -> {}
    }
}

Signature

class PakDownloadManager(downloader: PakDownloader, minimumDiskSpaceBytes: Long = 100L * 1024 * 1024, executor: Executor = defaultExecutor())

Constructors

PakDownloadManager ( downloader , minimumDiskSpaceBytes , executor )
Signature
constructor(downloader: PakDownloader, minimumDiskSpaceBytes: Long = 100L * 1024 * 1024, executor: Executor = defaultExecutor())
Parameters
downloader: PakDownloader  The 3p-provided network transport implementation.
minimumDiskSpaceBytes: Long  Minimum free disk space to maintain. Downloads fail with DownloadError.DiskFull if available space drops below this threshold.
executor: Executor  Executor on which downloads run. Defaults to a dedicated cached daemon-thread pool isolated from ForkJoinPool.commonPool so that long-running blocking I/O (network, disk) does not starve other application work that uses the common pool (e.g. parallel streams). Pass your own Executor to control concurrency or share a pool.

Methods

availableDiskSpace ( destination )
Checks available disk space at the destination path.
Signature
fun availableDiskSpace(destination: File): Long?
Parameters
destination: File
Returns
Long?  available bytes, or null if the path doesn't exist.
cleanup ( pakName , file )
Deletes the file for PakDownloadManager.cleanup and removes it from tracking.
Side effects:
Safe to call even if no download is active.
Signature
fun cleanup(pakName: String, file: File): Boolean
Parameters
pakName: String
file: File
Returns
Boolean  true if the destination file was deleted.
cleanupAndAwait ( pakName , file , timeoutMs )
Like PakDownloadManager.cleanup but blocks (up to PakDownloadManager.cleanupAndAwait) waiting for the cancelled download's worker thread to drain before returning. Use this when you're about to immediately re-start() a download for the same destination and want to guarantee the prior worker can no longer be holding any file handles or about to fire late callbacks.
Without this, a cleanup-then-start sequence races: the cancelled worker may still be inside downloader.download(...) when the new download begins, even though PakDownloadManager.cleanup already removed it from internal tracking. The lockedFiles map is identity-keyed so a stale removal cannot clobber a fresh lock, but having two workers racing on the same on-disk path during the brief overlap is still a footgun for filesystem behaviors like Windows file-locking.
Signature
fun cleanupAndAwait(pakName: String, file: File, timeoutMs: Long = 5000): Boolean
Parameters
pakName: String
file: File
timeoutMs: Long
Returns
Boolean  true if the destination file was deleted (same semantics as PakDownloadManager.cleanup).
cleanupStalePartials ( directory , maxAgeMs )
Deletes partial download files that are older than PakDownloadManager.cleanupStalePartials and not currently being written to. Call on app startup to garbage collect abandoned partials from prior sessions that crashed or were killed.
Three kinds of partial files are reaped:
  • <name>.pak.tmp \u2014 the canonical in-progress download path
  • <name>.pak.<nano>.tmp \u2014 alternate path used when the canonical tmp was a directory
  • <name>.pak.<nano>.publish \u2014 staging file used by the cross-volume copy fallback
Completed .pak files are NEVER deleted by this method even if they are older than PakDownloadManager.cleanupStalePartials, because completed paks may be mounted or in active use. The caller is responsible for managing the lifecycle of completed paks (e.g. via PakDownloadManager.cleanup).
Signature
fun cleanupStalePartials(directory: File, maxAgeMs: Long): Int
Parameters
directory: File  Directory to scan for partial files.
maxAgeMs: Long  Maximum age in milliseconds. Partial files with lastModified older than this are deleted.
Returns
Int  Number of files deleted.
destroy ()
Cancels all active downloads and clears internal state.
Signature
fun destroy()
getHandle ( pakName )
Returns the active handle for PakDownloadManager.getHandle, or null if no download is in progress.
Signature
fun getHandle(pakName: String): DownloadHandle?
Parameters
pakName: String
start ( request )
Starts a download described by PakDownloadManager.start. Returns a DownloadHandle for observation/control.
If a download for the same DownloadRequest.pakName is already active, returns the existing handle (deduplication).
Pre-flight checks performed before starting:
Validation failures are surfaced through the returned handle's terminal Failed state rather than thrown, so the caller's reactive flow handles them uniformly with runtime errors. The exception is structurally invalid input (a request that can't even be tracked — blank pakName) which is rejected with IllegalArgumentException at call time so the bug surfaces at the offending caller's site, not as a mysterious failed handle later.
Signature
fun start(request: DownloadRequest): DownloadHandle
Parameters
tryMount ( pakName , file )
Convenience method that mounts a validated file via PakManager. The 3p can call PakManager.mount() directly — this adds structured logging and a pre-mount existence check.
Signature
fun tryMount(pakName: String, file: File): PakManager.MountStatus
Parameters
pakName: String
file: File
Returns
PakManager.MountStatus PakManager.MountStatus — the raw result from PakManager. Returns PakManager.MountStatus.OpenFailed if the file does not exist at mount time. Note that this conflates "file genuinely missing" with native-side open failures — callers that need to distinguish should perform their own File.exists check before calling.
validateFile ( request )
Validates an existing file against the request constraints (size, pluggable validator) without downloading. Useful after app restart to check if a previously downloaded file is still valid before mounting.
Signature
fun validateFile(request: DownloadRequest): DownloadError?
Parameters
Returns
DownloadError?  null if valid, or a DownloadError describing the problem.