Repository navigation
Add optimole export: export the library and download its archives - #1
Merged
Merged
Conversation
New command group: - export start: start an export of the whole library (or follow the one already running), wait for it and download each ZIP archive as soon as the server lists it; --no-wait only starts it - export download [ID|latest]: download or resume an export - export status, export list, export delete Archives land in DIR/optimole-export-ID/DOMAIN/images-N.zip next to an export.json manifest (no URLs). Each archive goes to a .part file named after its listed size, is resumed with HTTP Range after a cut or a stall, gets a fresh URL when storage refuses one, and is renamed into place only once its size matches and it opens as a ZIP. An archive the server rewrote (new size) is downloaded again. Domain names from the server are turned into safe, distinct folder names on every OS. Conflicts on start use the dashboard's new export_in_progress and export_limit_reached codes and fall back to the export list on servers that answer a bare 409. Download URLs never get the API key, are never printed and redirects are refused, as for uploads. The fake server gains the export endpoints and an archive store with Range support and fault injection (cuts, stalls, ignored ranges). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live on staging (no error codes yet) a tiny export finished between the 409 and the look at the list, so the CLI saw no running export and gave up. Below the 5-export limit the only conflict is the newest export, so it is followed even when it has finished; a coded export_limit_reached is never mistaken for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Every file of an archive is read back with its checksum before the archive is put in place, so a resume that joined two same-size versions is caught and downloaded again from scratch. - Links planted in the export folder are not followed: domain folders must be plain folders, a link at a partial file is removed and the file opened with O_NOFOLLOW where available, export.json is written to a fresh O_EXCL file. - One command at a time per export folder: an OS lock (flock, LockFileEx) on .optimole-export.lock; the manifest is written only under it. - A failed look at the export cancels the downloads in flight instead of waiting for them; their partial files stay for the next run. - An archive whose size neither the export nor storage gives is refused. - Folder names are capped in bytes, not runes. - A --domain rerun keeps earlier downloads of other domains in export.json. - Ctrl-C during the conflict lookup of "export start" exits 130. - README: event order, --no-wait exit code, checksum and lock notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s resume hint - The checksum read stops when the run ends, keeping the partial file for the next run instead of holding the folder lock until it finishes. - An archive counts as already downloaded only in a plain domain folder, so a linked folder never passes for the export's; with no listed size, a plain file in place counts (only checked archives are renamed there), so a --domain rerun keeps size-less archives in export.json. - The "Resume with" hint quotes the folder for a shell: as is when plain, single quotes on Unix, double quotes on Windows (Go quoting doubled every backslash of a Windows path). - README: files in a compression format Go's ZIP reader lacks are not read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ckslashes on Windows Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stopping the download of an export that had already finished printed "Export N continues on the server", which is not true. Seen on staging: Ctrl-C while downloading the single part of a completed export. The resume command is printed alone now; a running or not yet seen export keeps the full hint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… timer The test cancelled after 200 ms, which on a slow runner could land before the CLI had read the export and so print the running-export hint. It now cancels when the archive request arrives, and the fake server's download delay ends when the client cancels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestRunLimitsParallelDownloads relied on a 30 ms download delay to keep three downloads overlapping, and on a busy Windows runner with -race only two overlapped. The first three downloads now wait in the server hook until all three have arrived, then 50 ms more, so a correct limit always shows 3 and a broken one shows 2 or 4. The fake's own download counter had no other user and is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A new
optimole exportcommand group that starts an export of the whole library on the dashboard and downloads its ZIP archives:export start [-o DIR] [--domain D]... [-c N] [--no-wait] [--timeout DUR] [--json]export download [ID|latest] [same flags]export status [ID|latest] [--json],export list [--json],export delete IDFiles land in
DIR/optimole-export-ID/DOMAIN/images-N.zip, plus anexport.jsonmanifest (no URLs).How it works
Follows the export while it runs (
internal/export). One coordinator goroutine checksGET /api/asset-exports/{id}(5 s, then 15 s, then 30 s) and runs up to-cdownloads. Archives download while the export is still building the next ones.Safe downloads.
images-N.zip.<listed size>.part. After a cut it resumes withRange; if storage ignores the range it starts over, and a 416 empties the part file. A 2-minute stall restarts the download.Fresh state from the server.
The export folder is protected.
.optimole-export.lock. The manifest is only written while holding it.export.jsongoes through a fresh O_EXCL file.Untrusted domain names. Folder names keep only letters, digits and
.-_, at most 100 bytes, with no leading or trailing dots and no Windows device names. Names that collide case-insensitively get-2,-3… in sorted order, so a rerun maps the same way.Conflicts on start. The dashboard's new codes are used (Codeinwp/optimole-service#1919):
export_in_progress+export_id: follow that export;export_limit_reached: say so and name the oldest export to delete.Servers without codes (production until #1919 ships) are handled through the export list. Below the limit, a bare 409 can only mean the newest export, which is followed even if it has finished since: staging hit that race live. If the create call may have reached the server but got no answer, it isn't repeated, and a running export is followed.
Same rules as uploads. Download URLs never get the API key, are never printed or written to the manifest, must be https (or localhost), and redirects are refused.
Interrupting is safe. Ctrl-C (exit 130),
--timeout(exit 1) and a failed status check all stop the downloads in flight. Their partial files stay for the next run, and the command prints a ready-to-pasteoptimole export download ID -o …with the path quoted for the platform's shell. It says the export "continues on the server" only when the export hadn't finished.Reviews
@ % =now quoted (9c335fc, trivial follow-up). Accepted: cmd.exe and PowerShell both expand%/$inside double quotes, and PowerShell keeps a doubled trailing backslash (same folder). No single Windows form fits both shells; noted inshellQuoteTests
go test -race ./...(3 runs)gofmt -l,go vet(darwin, linux, windows, freebsd)internal/api/exports_test.go,internal/export/export_test.go,internal/cli/export_test.go. The fake gained export endpoints and an archive store with Range, cuts, stalls, ignored ranges, expiring URLs, rewrites, size-less listings and a download delaygo test ./...optimole-e2e+…accounts, erased after): upload 3 files →export start --no-wait→export start(bare 409 → follows) → zip holds the 3 files byte for byte → re-download skips → status/list/deleteexport start.partkept, hint printed. The rerun fetched exactly the missing 99,495,683 bytes with a Range request to S3, and all 6 files match byte for byte. This run found the "continues on the server" wording for a finished export, fixed in 8990156Not proven
🤖 Generated with Claude Code