-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
-
Atomic endpoints. Every PikPak operation is one
suspendextension function onPikPakClient. No hidden orchestration: account pools, sync engines, polling loops, cleanup heuristics and CLIs belong to the caller. The test for a new feature is whether a caller could not reasonably build it on the atomic calls. - Hard problems inside. What every caller would otherwise get wrong the same way — session persistence and refresh, captcha re-authentication, retry, rate limiting, content hashing, OSS signing, connection budgets, link expiry — happens inside the client.
- Measured, not guessed. Budgets, deadlines and block sizes come from Measurements; a number without one is a bug in the documentation.
-
Multiplatform.
commonMaindepends only on multiplatform libraries: Ktor (compileOnly), kotlinx.coroutines, kotlinx.serialization, kotlinx-io, kotlinx-datetime, KotlinCrypto. Platform code is limited to the CDN client's engine tuning, the default session directory, and telling a failure before sending from one after.
PikPakClient ─────────────── account state: session, captcha token, rate limiter, root domain,
│ account gate, foreground count, host health and speeds, folder memo
│
├── endpoints (extension functions, one file per domain)
│ └── HttpEngine.request ── JSON API: rate limit, headers, login on demand,
│ captcha once, 401 once, retry
│ └── HttpEngine.sendRaw ── CDN / OSS: no PikPak headers, not rate limited,
│ 401/403 → UrlExpiredException, live body
│
└── playback
PikPakStreamReader → PikPakFileCache → PikPakFileHandle → RangeReader → sendRaw
-
PikPakClientholds configuration and the state every file on the account shares. Its own API is small:login,logout,prewarm,currentSession/sessionFlow,domain,clearFolderIdCache,close;probeDomainis an extension besidePikPakDomain. -
HttpEngine(internal) runs both pipelines over one retry loop. The JSON pipeline moves the official API hosts underdomainin one place, so endpoints keep namingapi-drive.mypikpak.comanduser.mypikpak.com; the CDN pipeline never rewrites a link's root. -
HostHealth(internal) is the account's view of CDN edge hosts: which went silent, how fast each delivers, which sibling a link may be sent to.RangeReaderasks it for every attempt's host and reports every attempt back.AuthApi(internal) owns the session ladder and the captcha signing. -
Endpoints are grouped by API domain:
Endpoints.kt(quota, listing, filters),FolderEndpoints.kt,SearchEndpoint.kt,StarEndpoint.kt,EventEndpoint.kt,ShareEndpoint.kt,ArchiveEndpoint.kt,UserEndpoint.kt,TransferQuotaEndpoint.kt,UploadEndpoint.kt,CidEndpoint.kt,MagnetResolveEndpoint.kt,UrlOfflineEndpoint.kt,OfflinePruneEndpoint.kt,Tasks.kt,VariantEndpoint.kt,RangeStreamEndpoint.kt,RangeDownloadEndpoint.kt. - Playback is described in Playback.
Break one of these and something fails only under concurrency, only on one platform, or only in production.
- Snapshot before the request, not after the failure. A request captures the session and captcha token it sends, builds its headers from those snapshots, and hands them to the refresh on failure; the refresh does nothing if the current value already differs. That collapses N concurrent failures into one handshake. A snapshot taken inside the refresh, or headers re-read from live state, break it — the first bit only on a two-core CI runner.
- Never retry once the response block has started, and never replay a POST whose effect is unknown. See Errors and Retries.
-
Releases run under
NonCancellable. A cancelled coroutine that must wait for a mutex to give a gate slot back would throw first and leak the slot for good; a gate that has lost them all hangs every later acquire. - The file's gate belongs to the handle, not to the reader. Reads on a retired reader keep running; a replacement with its own gate lets one link carry twice its cap.
- Gates are taken per file, then per account. That bounds one file to its budget of account slots.
-
A variant is chosen once and locked by
mediaId.resolveVariantis the only place that falls back to the original. - The gcid is the identity of content; a file id is a cache of it.
- A reroute never looks like a changed link. Every refresh a reader performs is keyed on the link, not on the host an attempt went to; a sibling's failure sets the sibling aside and never reaches the link's refresh, which would otherwise mint for nothing or, handed the same link back, fail the read.
- The foreground count is re-synchronised until it agrees with the demand. Several threads change it without a common lock, and a single check-then-set left the account's count wrong.
-
Priorities are integers on one scale shared by every file on the account, with named points (
INDEX,BLOCKING,READ_AHEAD,WARM,BACKGROUND_*). Free integers let a caller invert the order; the named points are the supported way to place a read. -
The cache tuning (block size, read-ahead, memory cap, wide-block threshold) is not public. The four are coupled by one check; four free parameters would hand callers an
IllegalArgumentExceptionfor a relation nobody told them about. -
close()is not suspending anywhere a player could need it: releasing input happens in lifecycle callbacks with no coroutine. -
The SDK writes nothing to disk on its own, the session store aside. Persistence of bytes is a
BlockStorethe caller supplies and decides for.
A full read of the SDK (every source file, four areas in parallel) produced 78 findings. The bugs, dead APIs and wrong documentation were fixed in 1.0.0 — see History for what was removed. What remains are shapes that work but are known to be wrong, in order of how much they cost:
-
Settled in 2.0.0, differently from what this item proposed. It suggested a sequential background cursor on the cache with an in-order sink; what shipped is a download as a warm whose blocks must reach adownloadTois a second scheduler.DurableBlockStore. An in-order sink cannot take the blocks a player fetches out of order — the head, then the index at the tail, then wherever it seeks — so it would either drop them or hold them in memory, and the file would still be fetched twice. A store keyed by block takes them as they come.downloadTostays for a caller who wants the plain file whose length is its progress. -
Two overlapping file models.
FileStat(listings) andFileDetail(getFile) repeat about ten fields and neither is a superset: a detail cannot say whether it is starred, a listing has no links. Callers refetch to cross over. One model with optional links and variants, or a shared interface, would end that. -
Pagination is written per endpoint. Each listing loop is its own
do { page } while (token), parameter order differs betweenlistFilesPagedandlistMyShares, and nothing guards a token that repeats — which matters because at least one filter (system_tag) is known to make the server ignore the page size. One internal pager would fix all of them. -
— the gcid identity and rebuild ladder, link minting with host avoidance, and ownership of the block cache. Split in 2.0.0, when downloads would have grown it again: the cache isPikPakFileHandlecarries three jobsPikPakFileCache, reading through the handle as anyRangeSource. -
One captcha token for every action.
captcha/initis asked per action, but the client keeps one token and reuses it across actions. It has always been accepted; if the server ever binds tokens to actions, the token and its refresh have to be keyed by action.
Two more from the review were breaking and went into 1.0.0 rather than wait for 2.0: the task record is DriveTask (it was OfflineTask, a name from the first task type the SDK read), and every gcid the SDK hands out is upper case (local hashing used to produce lower case, so a stored hash compared with == against a listing read as different content).
Consumers that shaped these choices: Piko (player behind a loopback proxy, random-clip feed, downloads, resumable uploads, magnets and offline packs, archives) and Animeko's PikPak engine.
Using the SDK
- Getting Started
- Best Practices
- Playback
- Magnets and Instant Create
- Drive Operations
- Uploads and Offline Tasks
- Errors and Retries
Understanding it
Working on it