-
Notifications
You must be signed in to change notification settings - Fork 0
Drive Operations
Everything here is a suspend extension on PikPakClient that makes one or a few API calls; the client logs in, rate-limits and retries underneath.
client.getQuota() // storage (GET /drive/v1/about)
client.getTransferQuota() // monthly upload, offline and download allowances, membership tier and expiry
client.getUserProfile() // nickname, avatar, contact details masked by the serverMembership is read off getTransferQuota, which carries the tier and its expiry beside the allowances.
client.listFiles(parentId = "") // every page, concatenated
client.listFilesPaged(parentId, pageToken = "", extraFilters = mapOf(FileFilter.kind(FileKind.FOLDER)))
client.getFile(fileId) // FileDetail, with links and media variants
client.getFolderId(parentId, name) // an immediate child folder by name
client.getDeepFolderId(parentId, "a/b/c")
client.getPathFolderId("/a/b/c") // the same from the root
client.getOrCreateDeepFolderId(parentId, "a/b/c") // mkdir -pTwo models: FileStat from listings (tags, thumbnail, times, deleteTime) and FileDetail from getFile (links, medias). Neither is a superset of the other; see Architecture.
Fact (2026-09-02). The filters query accepts kind, trashed (a real boolean — the string "false" is a different filter), phase, starred, modified_time and system_tag, and nothing else. name answers 404 under every operator tried (eq, like, contains, in, prefix): the field itself is refused. The q, search_text and name query parameters are accepted and ignored.
Resolved folder paths are memoized on the client. Every mutation the SDK makes drops the memo — surgical invalidation would have to model every path that could run through a renamed segment — and so does clearFolderIdCache() after a change made elsewhere. A lookup already on the network when the memo was dropped does not write its answer back. getOrCreateDeepFolderId is serialised within one client, so two coroutines needing the same missing folder create it once; two devices can still both create it, and PikPak allows duplicate names in one folder, so the result is two folders — treat "first match wins".
Fact (2026-09-27). Listings trail mutations by 0.1–0.8 s; see Measurements.
client.createFolder(parentId, name)
client.rename(fileId, newName)
client.deleteFile(fileId) // permanent
client.batchTrash(ids) // recoverable until each item's deleteTime
client.batchUntrash(ids) // back to the original parent
client.batchDelete(ids) // permanent
client.batchMove(ids, toParentId)
client.batchCopy(ids, toParentId) // server tasks; small copies are done on returnFact (2026-09-12). One batch call may name about 200 ids; 1000 answer operating_file_count_exceeded (error_code 11). Every batch call splits long lists into calls of 100. Calls go in order, and one that fails leaves the ones before it applied.
Fact (2026-09-23). DELETE /drive/v1/files/{id} bypasses the trash: the file answers 404 right after and is not in the trash.
Fact. The trash keeps an item until its FileStat.deleteTime, which the server sets: fifteen days after trashing on a platinum account. Read it rather than assume a period.
Fact (2026-09-24). Copying or moving an item into its own parent or its own subtree is refused with file_move_or_copy_to_cur (error_code 9). A copy runs as a task of type copy; one small folder was already complete when the call returned. Nothing larger was measured, so check the task before assuming.
client.listTrash()Fact (2026-09-23). The trash listing needs parent_id=*; without it the server lists only items trashed from the root, and an item trashed from a subfolder is in the trash, restorable, and missing from the listing. A trashed item's detail fails with error_code 9, file_in_recycle_bin — the same code as a captcha, told apart by name.
client.starFiles(ids)
client.unstarFiles(ids)
client.listStarred() // parentId "*" (default) for the whole drive, a folder id for its direct children
file.isStarredFact (2026-09-24). A star shows only as a STAR entry in a listing's tags; the starred field of the detail response stayed false on starred items. listStarred filters on system_tag {"in": "STAR"}, as the web client does, and the server ignores the page size under that filter.
client.searchFiles("ep01", parentId) // one folder deep
client.searchFilesRecursive("ep01", limits = RecursiveSearchLimits(maxDepth = 3)).collect { hit ->
println("${hit.path} (${hit.file.id})")
}PikPak has no name search on any endpoint (see the filters above; the web client's own search runs in a Web Worker over an IndexedDB cache). A subtree search is one listing per folder, breadth-first on the client so shallow matches — usually the wanted ones — arrive first. RecursiveSearchLimits bounds depth (8), folders listed (2000), wall time (60 s) and concurrency (4); exhausting any ends the flow normally with the hits so far. At the default rate limit about 300 listings fit in a minute, so the timeout binds first. Each hit carries the folder names between the root and itself, because a drive-wide search otherwise returns identical names.
FileStat.params.url (sourceUrl) holds the magnet for anything an offline task produced — the only place that association is recorded, since tasks cannot be queried by URL or info hash — or the https://mypikpak.com/s/<shareId> link for anything restored from a share (shareIdFromUrl). Every file carries its gcid in hash, offline products included.
val share = client.createShare(listOf(fileId), requirePassCode = true) // share.shareUrl, share.passCode
client.listMyShares()
client.deleteShares(listOf(share.shareId))
val shareId = shareIdFromUrl(url) ?: return
val info = client.getShareInfo(shareId, passCode = "zq47") // top level, and a pass-code token
client.listShareFiles(shareId, info.passCodeToken, parentId = folderId)
client.restoreShare(shareId, info.passCodeToken, fileIds, toParentId) // a task of type restoreReading a share needs no login. An unreadable one still answers HTTP 200 and names the reason in its status — pass code missing or wrong, share cancelled — so the read calls throw ShareUnavailableException rather than return an empty folder. GET /share ignores parent_id; folders below the top level go through listShareFiles with the token.
Fact. A restore finished in seconds and the copies landed directly in toParentId, without the share's folders above them. The task names no new ids (the trace_file_ids the web client reads was absent), so a caller finds the copies by listing the destination; each keeps original_share_id, original_file_id and the share link in params. Restoring one's own share fails with file_restore_own, error_code 9 (2026-09-24).
client.listPlayHistory() // newest first, 100 a page
client.reportPlay(fileId, positionSeconds = 754, durationSeconds = 1420)
client.deleteEvents(listOf(eventId))
client.clearEvents(listOf(EventType.PLAY))
client.listEvents(listOf(EventType.UPLOAD, EventType.RESTORE))The history is the one the official clients keep, so a position reported here resumes there and back. A file has one play event: reporting again overwrites its position, smaller or not, and moves it to the top. The server drops a report that follows the previous one for the same file too closely and still answers {} — 1.5 s apart was dropped, 6 s kept — so report no more often than every five seconds, as the web client does. Each event carries its file in the listing shape, without links. An unfiltered event listing holds uploads and restores but no plays.
client.listArchive(file.id, file.hash, path = "", password = "") // one level; folder paths end in "/"
val task = client.decompressArchive(file.id, file.hash, toParentId, paths = listOf("Extras/"))
client.getDecompressProgress(task.taskId) // 0-100; fileId is the new folder
val tree = file.archiveTree ?: return // after the first decompress
client.listArchiveTreePaged(tree)
client.getArchiveTreeFile(tree, entryId) // a signed link to one entry
client.copyFromArchiveTree(tree, entryIds, toParentId) // extract the picked entries
client.packFolder(folderId) // a folder becomes one .tar, in place
client.unpackFolder(fileId) // and backzip, encrypted zip, 7z and rar list and extract; tar does not. Both paths want the password even when only the contents are encrypted, and throw ArchivePasswordException without it. A browsable tree appears only after an archive has been decompressed once.
Fact (2026-09-25). The server cannot extract multi-volume archives (7z, zip and RAR5 volumes): it reads only the file it was given, never its siblings. Most volumes answer INVALID_FILE_FORMAT at once; the last volume of a spanned zip lists its contents, and extracting it then fails with E_INVALID_FORMAT.
Fact (2026-09-24). packFolder keeps the folder's id, adds .tar to its name and turns it into a file whose children disappear from listings without being deleted; unpackFolder brings the same folder and child ids back. Right after the call the file has size 0 and no gcid; both are set once the task (type pack_files) completes. An empty folder is refused with cannot_pack_empty_folder, a file with invalid_argument.
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