Skip to content

S3 Explorer v0.3.8

Latest

Choose a tag to compare

@cbarange cbarange released this 08 May 17:03
67114a1

S3 Explorer — manage S3-compatible object storage from your desktop

Cross-platform Tauri + Vue 3 client supporting AWS S3, MinIO, Backblaze B2, Cloudflare R2, Dell ECS, and 17+ other S3-compatible providers.

What's new in v0.3.8

Compatibility release. v0.3.7 was skipped — its work is folded into this version. Three independent improvements ship together: real S3-compat support, a much better network diagnostic, and a more usable browsing UI on long paths.

🪣 Real S3-compat — fix DeleteObjects on Dell ECS, MinIO, Cloudflare R2, Wasabi, Backblaze B2, Tigris…

A user on Dell ECS reported Operation failed: invalid request when deleting a folder with two files. The root cause turned out to be far broader than one provider:

The AWS SDK Rust 1.69+ flipped RequestChecksumCalculation from WhenRequired to WhenSupported, injecting a x-amz-sdk-checksum-algorithm: CRC32 header + x-amz-checksum-crc32 trailer over aws-chunked body encoding on every supporting operation. AWS S3 itself accepts both modes, but most S3-compat servers don't recognise the modern flexible-checksum framework and reject the request — Dell ECS as InvalidRequest, older MinIO as Missing required header: Content-MD5, Cloudflare R2 as NotImplemented: Header x-amz-checksum-crc32, and so on.

v0.3.8 ships a defense-in-depth fix:

  • SDK upgradeaws-sdk-s3 1.17 → 1.132, aws-config 1.1 → 1.8 (5 minors of upstream fixes, plus the future-proofing ChecksumAlgorithm::Md5 enum landing).
  • RequestChecksumCalculation::WhenRequired — disables auto CRC32 injection on every operation that doesn't strictly need it (PutObject, ListObjectsV2, GetObject…). Restores the pre-2025 SDK behaviour that S3-compat servers know how to parse.
  • New LegacyChecksumStripper interceptor — for the few operations like DeleteObjects whose Smithy trait declares httpChecksumRequired: true and ignore WhenRequired, this Smithy interceptor strips x-amz-checksum-*, x-amz-trailer, and x-amz-sdk-checksum-algorithm headers before transmit. Last line of defense.
  • map_s3_error enriched — when an S3-compat server rejects a request, the user now sees the server's actual message (e.g. InvalidRequest: <Dell ECS message exact>) instead of our generic explanation. extract_server_message parses the <Message> field from the SDK's debug repr.

🔍 Network diagnostic — multi-step pipeline

The connectivity test in Settings → Advanced → Network was a single pass-or-fail probe — useful but opaque. v0.3.8 turns it into a full diagnostic pipeline:

Step What it checks What it tells you
DNS resolution Resolves api.lemonsqueezy.com via system + DoH cascade (Cloudflare 1.1.1.1 + Google /resolve) All IPs returned, suspicious responses (loopback / RFC1918), latency
TCP connect Opens a socket to each resolved IP Per-IP connect time, happy-eyeballs IPv4↔IPv6 fallback if one family is broken
TLS handshake Negotiates TLS through your configured proxy + OS trust store Certificate chain length, verified subject, cipher suite
API contract HEAD /v1/licenses/activate Confirms the endpoint actually responds (catches transparent gateway errors)
Sanity Response analysis Detects body redirection / captive portals

Each step shows its outcome (ok / warning / error / skipped), a human message, structured details, and elapsed milliseconds — so when the License tab fails, you can see exactly which layer broke.

Other related fixes:

  • DNS multi-IPresolve_with_fallback now returns Vec<IpAddr> instead of Option<IpAddr>. Some hosts (especially in corporate networks behind /etc/hosts overrides or split-horizon DNS) have multiple A/AAAA records, and we previously dropped all but the first — breaking happy-eyeballs IPv4↔IPv6 fallback.
  • Coherent HTTP clientbuild_http_client_for_host factored so license activation and the diagnostic share the same TLS/proxy/DNS configuration. No more "diagnostic says OK, activation fails".

🧭 Adaptive breadcrumb

The breadcrumb in ObjectBrowser had two failure modes on long paths: it overflowed off the right edge (cutting the current folder, the most important segment), and it hid intermediate segments under ... without a way to read them.

v0.3.8 reworks it as an adaptive component:

  • 5-step truncation strategy based on the actual toolbar width (measured via ResizeObserver):
    1. Show all segments full
    2. Collapse middle: [first, "...", last]
    3. Truncate first: [first_t, "...", last]
    4. Drop first entirely: ["...", last]
    5. Last resort: ["...", last_truncated_with_ellipsis]
  • The current folder is always visible — truncated mid-name (hhh...hhh) only when the window is genuinely too narrow.
  • truncateMiddle(text, max) + truncateEnd(text, max) helpers in formatters.ts.
  • The pill is content-sized — when the path is short, the freed space goes to the search bar (which had a needless max-w-md cap, now removed).
  • Click-to-edit mode keeps the pill's exact width so the box doesn't shrink/grow when toggling between view and edit.

📂 Indexation behaviour — explicit "no auto-indexing"

Settings → Performance → max indexing requests had a 1-100 range, with no way to disable indexing for very large buckets where the user just wants to navigate. v0.3.8 expands this to 0-5000:

  • 0 = no indexing on bucket open at all (counts/sizes display as , manual Full Index button still works)
  • 1-5000 = caps the auto-indexing at that many ListObjectsV2 calls
  • The Full Index button uses an internal u32::MAX sentinel to bypass any cap

🪟 UI compaction

  • BucketList — bucket creation date moved from the card body into a tooltip on the bucket name. Card padding tightened from p-3 to px-3 py-2. Saves ~16-18 px per card → roughly one extra bucket visible on screen.
  • Sidebar footer — Metrics & Settings buttons row padding p-4p-2. Footer height 68 px → 52 px (-16 px = ~1 bucket row reclaimed for the list).

🐛 Licensed users no longer see the purchase popup

The NagPopup (5 min timer + click-based trigger that nudges unregistered users to buy a license) was reading the frontend license store at trigger time. The store was only hydrated when the user opened Settings → License — at boot it stayed at 's0' (unregistered), so the popup fired even on machines with a valid licence active in profiles.json.

App.vue now hydrates the store via licenseStore.checkStatus() on mount, before any popup timer can fire. The backend remains the source of truth (license stays validated against Lemon Squeezy, machine-bound, tampering-detected) — we just stopped reading a stale frontend cache.

Internal

  • AWS SDK upgrade brings 5 minors of upstream fixes for free (retry logic, IMDS handling, DNS caching).
  • New file s3_compat.rs — small, self-contained Smithy interceptor with unit tests.
  • s3_errors.rs gained extract_server_message + 4 unit tests covering the parsed/empty/long/missing cases.
  • 74 cargo tests passing.

Downloads

Platform Download
macOS (Universal — Intel + Apple Silicon) .dmg
Windows x64 .exe · .msi
Windows ARM64 (Snapdragon, Surface Pro X) .exe · .msi
Linux x64 .AppImage · .deb · .rpm
Linux ARM64 .AppImage · .deb · .rpm

Existing v0.3.6 users on Linux/Windows will receive v0.3.8 automatically via the in-app updater. macOS users will receive it once the macOS bundle is uploaded.