github axpdev-lab/aeroftp v4.0.0
AeroFTP v4.0.0

4 hours ago

[4.0.0] - 2026-05-27

Shaped Graph Transfer (DAG) and Multi-User Account Partition

v4.0.0 lands two architectural shifts in the same release.

The first is the promotion of the ready-frontier DAG transfer engine introduced in v3.8.4 to the single production path for every transfer surface. The three rollout flags that gated the engine during the v3.8.x cycle are gone, the hand-rolled JoinSet batch orchestrator has been deleted, and the shaped builders are now the single source of truth for every graph the executor schedules: single-file leaves, multi-file batches, sync sessions, intra-file segmented downloads, and cross-bucket copies. The release introduces a principled trait expansion on StorageProvider so the engine can dispatch the right shape per call from a provider's TransferCapabilities, ships the wiring + integration tests that prove the shapes work against live S3, B2, WebDAV, Azure, ImageKit, Google Drive, Dropbox, OneDrive, Box, MEGA, pCloud, kDrive, OpenDrive, Yandex Disk, Jottacloud, Drime, Uploadcare, Cloudinary, and Filen, and pairs the architectural shift with a power-user CLI surface that exposes 25+ runtime knobs over the same engine.

The second is the Multi-User Account Partition: the vault splits into per-user partitions while remaining fully backward-compatible with single-user installs. A boot-time Account Lock Screen presents the configured users, the vault is partition-aware end-to-end (server profiles, AeroSync settings, CLI --user flag), and an admin role with a self-or-admin gate plus an admin reset-passphrase backend rounds out the surface. Migration from a v3.8.x single-user keystore is automatic and idempotent, the admin role is opt-in with a last-admin guard, and the unlock prompts use honest crypto-stack labels (Argon2id key derivation, AES partition encryption). Sixteen commits cover the partition foundation, the boot picker, the CLI --user flag across profile and transfer commands, the cross-user dedup probe with HMAC keying, the migration of every existing keystore consumer (ConnectionScreen, SettingsPanel, IntroHub, ExportImport, KeystoreWizard, Cloud, SavedServers, App.tsx), the admin role, and the UX polish (logout, perceptible unlock spinner, refined L2 picker).


What's new

Capability-aware shape per transfer

The shaped graph builder picks the transfer-core shape per call from the provider's capability snapshot:

  • Native multipart upload fan-out on S3, Backblaze B2, Google Drive, Dropbox, OneDrive, and Box: an upload above one preferred chunk now produces N UploadPart graph nodes (one per chunk). The runner orchestrates the lifecycle end-to-end. Per-provider live validation: Drive 500 MiB in 34.6 s (63 parts x 8 MiB), Dropbox 500 MiB in 54.2 s (63 parts x 8 MiB concurrent), OneDrive 500 MiB in 63.8 s (50 parts x 10 MiB sequential), Box 100 MiB in 24.6 s (13 parts x 8 MiB, SHA-1 per chunk + whole-file SHA-1 commit).
  • Server-side copy on every backend that advertises the capability: S3 x-amz-copy-source, B2 b2_copy_file, WebDAV RFC 4918 COPY, ImageKit copyFile, plus 14 other native providers. The shaped-copy graph collapses to a single ServerSideCopy node that reserves only an api_slot: no file slot, no disk I/O, no local host bytes. The server moves the data. S3 sources above the 5 GiB CopyObject limit fan out into parallel UploadPartCopy requests (T-DEBT-08).
  • Intra-file segmented downloads through the shared shaped_ranges builder: when a provider proves it honours HTTP Range, the segmented download fans out into N DownloadRange nodes with no inter-segment dependencies, governed by the shared chunk / HTTP / disk-write budget.

For users on backends that advertise none of these capabilities the engine degrades honestly to the same single-transfer-core path that shipped pre-v4.0.0, byte-identical with the legacy provider.upload / provider.download.

Provider trait expansion

Five new methods extend StorageProvider:

  • begin_multipart_upload(remote_path, total_size, content_type, local_source_path)
  • upload_part(handle, part_number, data)
  • complete_multipart_upload(handle, parts)
  • abort_multipart_upload(handle)
  • server_side_copy(from, to) (alongside the existing supports_server_side_copy() capability gate).

Default implementations return NotSupported, so a provider that never advertises a capability never reaches the new methods. Nineteen native backends already implement them: S3 (multipart + server-side copy), B2 (multipart + 5 GB-capped server-side copy), Azure Blob (Put Block / Put Block List), WebDAV (Nextcloud chunked v2 + RFC 4918 COPY), ImageKit (copyFile / copyFolder), Google Drive (resumable session), Dropbox (concurrent upload_session + explicit close), OneDrive (Microsoft Graph createUploadSession), Box (chunked v2 with per-chunk SHA-1 + whole-file SHA-1 commit), MEGA (gfs* canonical chunk ramp), pCloud (chunked upload session), kDrive (upload-session API), OpenDrive (chunked upload session), Yandex Disk (upload-target API), Jottacloud (allocate API), Drime (S3 multipart), Uploadcare (multipart API), Cloudinary (chunked upload), Filen v3 (chunked AES-GCM encrypted upload). ImageKit / Internxt / MEGA / 4shared / FileLu document the trait as NotSupported-by-design for cases where the protocol does not offer a real multipart surface.

Power-user CLI knob expansion (PR #261)

Twenty-five new flags expose the same DAG engine to scripted workflows and CI pipelines without touching code:

  • Generic (Sprint K1 Pacchetto A): --sftp-concurrency, --checkers, --tpslimit / --tpslimit-burst, --no-traverse, --order-by.
  • S3 surface (KE-B1): --s3-upload-concurrency, --s3-no-check-bucket, --s3-disable-checksum, --s3-acl, --s3-storage-class.
  • Retry-After parsing extensions (KE-E): S3, Azure and Filen now parse and honour server-issued Retry-After on 429 / 503 throttle responses (joining Google Drive, Dropbox, OneDrive and Box from T-DEBT-05).
  • Drive non-AIMD knobs + AIMD config (KE-B2): pacer / abuse acknowledgement / copy-related runtime settings for Google Drive.
  • OneDrive (KE-B3): --onedrive-no-versions, --onedrive-list-chunk, --onedrive-link-scope.
  • Azure (KE-B4): --azure-upload-concurrency, --azure-disable-checksum, --azure-access-tier, --azure-archive-tier-delete.
  • AIMD adaptive concurrency (KE-D): --aimd-disable kill switch, --aimd-min / --aimd-max / --aimd-step-window overrides + per-class TOML configuration, hint table propagation through the executor.
  • FUSE mount surface (Sprint K3 / T-DEBT-13): --cache-mode={off|minimal|writes|full} policy preset, polling / timeout knobs, --write-back-cache via Filesystem::init capability negotiation, --fuse-threads multi-threaded session loop. fuser bumped 0.16 to 0.17.

AeroCloud catch-up (PR #262)

  • Koofr background sync dispatch fix. Koofr was already a stable provider in the AeroCloud wizard but cloud_provider_factory.rs did not route it to the background sync path, so a Koofr profile selected from the wizard would surface "provider not implemented" at first scheduled sync. One-line factory dispatch fix.
  • Four providers exposed in the AeroCloud wizard: ImageKit, Uploadcare, Cloudinary, Backblaze B2. They were already wired through the factory since v3.x but never appeared as selectable presets in the wizard UI, leaving them reachable only by hand-rolled config.

Convergence cleanup

  • The three AEROFTP_TRANSFER_ENGINE_DAG_* environment-variable flags are removed.
  • The matching dag_single_file_enabled() / dag_batch_enabled() / dag_sync_enabled() functions are removed.
  • should_route_sync_to_dag(dry_run) collapses to !dry_run.
  • The four engine-routing shims in provider_commands, the CLI, the batch orchestrator and the sync path drop their flag check.
  • The hand-rolled JoinSet sliding-window orchestrator in transfer_orchestrator::execute_batch (130+ lines including spawn_transfer_task) is removed: execute_batch_dag is the entire body.

Multi-User Account Partition (PR #279)

A second architectural pillar that splits the vault into per-user partitions while keeping single-user installs fully backward-compatible:

  • Encrypted partition foundation with per-user data isolation, Argon2id key derivation, and AES partition encryption.
  • Boot-time Account Lock Screen that presents the configured users and accepts a per-user passphrase, with honest crypto-stack labels in the unlock prompts.
  • Partition-aware vault wiring end-to-end: SavedServers, App.tsx, ConnectionScreen, SettingsPanel, IntroHub, ExportImport, KeystoreWizard, Cloud, favicon.
  • Per-user AeroSync settings CRUD on top of the partition layer.
  • Admin role with a self-or-admin gate and an admin reset-passphrase backend, last-admin guard so an installation cannot lock itself out.
  • CLI --user flag across profile and transfer commands; optional everywhere, defaults to the active profile.
  • Cross-user dedup probe with HMAC keying.
  • UX polish: logout flow, perceptible unlock spinner, refined L2 picker, account avatars with emoji/color customization.
  • Migration from a v3.8.x single-user keystore is automatic, idempotent, and preserves all existing data; the admin role is opt-in.

Fixed

DAG engine

  • Per-provider chunk size honoured verbatim: the runner consumes the provider's advertised multipart_part_size as the per-part length verbatim (last part takes the remainder); the legacy div_ceil distribution stays as the fallback. Fixes Google Drive 256-KiB alignment and OneDrive 320-KiB alignment that previously hit HTTP 503 mid-stream.
  • Serial chain for max_chunk_slots = 1 providers: the builder chains UploadPart nodes serially whenever the cap is one, preserving Drive's monotonic Content-Range contract; providers with parallel chunk fan-out (S3, B2, Dropbox concurrent, Box chunked v2) keep the unconstrained shape.
  • Dropbox concurrent session explicit close: complete_multipart_upload now emits the no-op upload_session/append_v2 with close: true before the finish call, so Dropbox no longer returns HTTP 409 concurrent_session_not_closed.
  • DAG single-file lock race (closes #233): provider_disconnect and provider_connect drain a per-transfer in-flight counter via TransferOperationGuard before mutating the provider slot.
  • DAG batch progress accounting (closes #234): a transient session_pool.acquire() failure during a batch transfer now increments the failed counter on the BatchProgressSnapshot and emits a batch_progress event before the node completes.
  • AIMD honours server-provided Retry-After (T-DEBT-05): Google Drive, Dropbox, OneDrive and Box now parse their flavour of the hint and propagate it across the provider/executor boundary through a marker convention. The executor extracts it and forwards it to AimdController::on_congestion_with_hint, clamped to a [1 s, 10 x default cooldown] safety band.

Community-reported fixes

  • Image preview clipped edges after zoom-in then zoom-out (#239, reported by @EhudKirsh). Scrolling the wheel on a previewed image silently switched the viewer out of Fit-to-screen mode. Wheel and toolbar zoom now act purely as a multiplier on top of the active Fit / Actual-size mode; the pan offset is reset when zoom returns to fit.
  • Windows installer overwrote user PATH (#240, reported by @miguelsotobaez). Critical regression affecting every Windows installer from v3.6.4 through v3.8.5. The post-install NSIS ReadRegStr + WriteRegExpandStr pattern silently truncated user PATH values larger than NSIS_MAX_STRLEN (8192 chars in the Tauri large-strings build), wiping every previously registered toolchain entry. Replaced with the EnVar NSIS plugin (zlib licence) which talks to the Win32 registry directly, preserves the original value type, and is idempotent.
  • AeroFTP did not boot on macOS Intel (#241, reported by @reset131). A lookbehind regex from mdast-util-gfm-autolink-literal@2.0.1 shipped untranspiled in the production bundle threw SyntaxError: Invalid regular expression: invalid group specifier name on JavaScriptCore <= 16.4 (Big Sur Safari 15), leaving React unmounted. Patched via patch-package to substitute \b for the lookbehind; the previous() helper in the same file already gates on the previous character so the substitution preserves all match offsets. Also clamps the initial window size to the primary monitor, pins chrome rows and main / panel flex children for small-screen layouts, and rebuilds the macOS icon.icns from the rocket source so Finder stops showing the stale orbit-only mark.

Changed

  • macOS Intel build restored (x86_64-apple-darwin). The Intel DMG, dropped earlier in the v3.6.x cycle as a recurring source of flaky bundle_dmg.sh failures, is back now that the Big Sur boot regression (#241) is fixed and confirmed working on a Big Sur Intel MacBook. The release ships a native Intel DMG (built on the macos-13 Ventura runner) alongside the Apple Silicon DMG (macos-14 Sonoma, aarch64); minimumSystemVersion stays at 10.13 so Big Sur is covered. A single universal binary is planned for a later release.
  • My Servers screen lagged on Windows (#221, reported by @raelb). Five optimisations let React.memo skip re-rendering server cards whose own data did not change: stable drag callbacks (signature change to (idx, e) => void), useMemo'd id maps for findIndex and crossProfileSelection, search-text cache, pre-allocated context menu icons.
  • OpenDrive folder privacy returned HTTP 400 (#252, reported by @EhudKirsh). folder/setaccess.json requires with_child_files alongside session_id / folder_id / folder_is_public; the matching file/access.json endpoint does not, which is why setAsPrivate / setAsPublic worked on files but failed on folders. Pass with_child_files=true so the requested privacy propagates to inner files.
  • OpenDrive privacy state not surfaced in the GUI (#252 pts 3 and 4, reported by @EhudKirsh, PR #280). OpenDrive was stashing the raw Access value under metadata but never populating RemoteEntry.permissions, so the context menu rendered both Set as Private and Set as Public with one disabled and the Properties Permissions tab showed the Not available empty state for files that actually carried visibility metadata. A new access_to_permissions(raw) helper maps the documented numeric levels (0 = private, 1 = public, 2 = hidden) to AeroFTP-canonical tokens, the raw integer stays under metadata["opendrive_access"] for diagnostics, and both folder_to_entry and file_to_entry now populate entry.permissions. The Properties Permissions tab gained a privacy-aware branch that renders a labeled Visibility row with a human description, and OpenDrive + 4shared context menus now render the applicable item conditionally instead of greying out the inapplicable one (matching the FileLu precedent from #161).
  • OpenDrive hidden visibility level unreachable from the GUI (#252 pt 2, reported by @EhudKirsh, PR #282). OpenDrive documents three visibility levels for both files and folders (0 = private, 1 = public, 2 = hidden) via the official API samples; the existing binary Set as Private / Set as Public toggle only flipped between two of them, leaving hidden unreachable. A new Privacy... entry in the OpenDrive context menu opens a three-option chooser dialog (radio list with per-level description, current badge on the active level, Save disabled until the user changes the selection). The new OpenDriveAccessLevel enum and set_file_access / set_folder_access methods drive the same file/access.json and folder/setaccess.json endpoints with the typed level; the existing binary set_file_privacy / set_folder_privacy become thin wrappers, so the wire shape of the toggle does not change. OpenDrive accepts the richer per-axis flags (folder_public_upl, folder_public_display, folder_public_dnl) only on folder/create.json, never on folder/setaccess.json, so modifying them after creation is intentionally not exposed.
  • MEGAcmd non-WebDAV: recursive delete timed out and Windows uploads failed (#263, reported by @EhudKirsh). Folder deletes hit a daemon timeout before completion, and uploads dispatched from a Windows local path (Ctrl+C / Paste workflow) never resolved the source. PR #265 unblocks both paths on the non-WebDAV MegaCmd flavor.
  • MEGAcmd WebDAV bridge: image preview failed on single-file resources (#264, reported by @EhudKirsh). The WebDAV client only knew how to PROPFIND a collection root and walk children, so the MEGAcmd WebDAV bridge, which serves a single file per URL, broke on every preview attempt. PR #269 adds a single-file-mode probe at connect(): a PROPFIND on the configured URL returns 207 with a non-collection resourcetype, the parsed entry is cached, and list / stat / build_url short-circuit to operate on the URL verbatim. Traditional WebDAV servers fall through to the existing PROPFIND / flow unchanged.

CLI hardening

  • aeroftp-cli Windows stack overflow at launch (PR #267). The ~80-variant clap-derived enum overflowed the 1 MB Windows default thread stack before main() could run. Adds /STACK:8388608 link arg via build.rs, gated on target_os = "windows" and scoped to the aeroftp-cli binary only. Pre-existing latent bug, surfaced and patched during PR #265 validation by the Windows debug runner.

Added

  • MEGAcmd storage quota via mega-df (#253, reported by @EhudKirsh). A helper that surfaces storage quota for MEGA accounts through the official MEGAcmd daemon, with automatic daemon warm-up on first request. Replaces the WebDAV walk for MEGAcmd profiles (which missed Device Centre) and adds the same surface to the non-WebDAV MegaCmd flavor. Translated across all 47 locales.

Documentation

  • New canonical technical reference at docs/DAG-TRANSFER-ENGINE.md.
  • New long-form architecture walk-through at docs.aeroftp.app/architecture/dag-transfer-engine.
  • AGENTS.md gains a Transfer Engine section spelling out the three shapes the engine picks per call.
  • New Privacy and Visibility Controls section in docs/PROTOCOL-FEATURES.md (#252 pts 6 and 7, reported by @EhudKirsh, PR #281). Documents the independent axes that contribute to privacy in a cloud-sync app (encryption at rest, default visibility on create, granular toggles, share-link semantics, IP controls, AeroVault overlay), per-provider behavior for OpenDrive / 4shared / FileLu / Filen / Internxt / MEGA / OAuth catch-all, an OpenDrive REST-API-vs-WebDAV reliability note covering the per-IP rate-limit and blacklist behavior on session/login.json, and the explicit disclosure that the extended OpenDrive flags (folder_public_upl, folder_public_display, folder_public_dnl) are accepted only on folder/create.json. The Advanced Operations matrix Privacy Toggle row, previously listing only FileLu, now also lists 4shared and OpenDrive.

Compatibility

The MCP tool surface is unchanged: same names, same arguments, same notifications. Progress events are now sourced from the engine's per-node lifecycle, but downstream consumers see the same JSON shape and the same event cadence. The CLI exit codes and the GUI transfer_event channel are likewise unchanged. The Multi-User migration is forward-only and idempotent: existing single-user installs keep their data on the first boot under a synthesised default user; invocations of aeroftp-cli without --user continue to target that user, so existing scripts keep working.

Why v4.0.0

The version bump reflects the architectural shift: the production transfer path is now a single, provider-agnostic, capability-aware DAG scheduler with five new trait methods on the public StorageProvider API, paired with a power-user CLI knob surface that exposes the same engine over 25+ runtime flags. Three flags, four shims, and the legacy batch orchestrator are gone. The Multi-User Account Partition is a schema-level change in the vault and an authorization-level change on the management API; bumping to v4.0.0 makes both the migration and the surface change explicit. The destructive admin reset, the last-admin guard and the OS-style lock screen are the load-bearing pieces of the new account surface, every one of them audited (MU-SEC P1) before landing. The convergence is complete; the cleanup pass for provider_transfer_executor.rs is filed as accepted technical debt for the post-v4.0.0 window (see docs/dev/roadmap/APPENDIX-DAG-ENGINE/STATUS_TODO.md).


Dependency updates landed in this release

  • serde_json 1.0.149 to 1.0.150
  • similar 3.1.0 to 3.1.1
  • rpassword 7.5.2 to 7.5.3
  • tar 0.4.45 to 0.4.46
  • postcss 8.5.14 to 8.5.15
  • vitest 4.1.6 to 4.1.7
  • dompurify 3.4.2 to 3.4.5
  • fuser 0.16 to 0.17 (T-DEBT-13)

Held back intentionally: russh 0.60.3 to 0.61.1 (pre-tag risk on the SFTP path, post-v4.0.0 candidate), codecov/codecov-action 6.0.0 to 6.0.1 (CI workflow check is failing on every PR, needs a separate investigation).


Downloads:

  • Windows: .msi installer, .exe, or .zip portable (no installation required)
  • macOS: .dmg disk image
  • Linux: .deb, .rpm, .snap, or .AppImage

Download AeroFTP

Don't miss a new aeroftp release

NewReleases is sending notifications on new releases.