Multi-Hoster-Upload 2.0 (Tauri 2 / Rust rewrite)
OTP (Doodstream two-factor):
- src/otp.rs: OtpBroker registers per-request oneshot channels with
180s timeout, survives abort via Cancelled answer.
- Doodstream login now loops: first attempt without OTP; if server
says OTP required, emit 'otp-required' event to the renderer with
a request_id, wait for provide_otp/cancel_otp commands, re-POST
with the code. Renderer can pop a modal on otp-required.
- UploadCtx carries the broker + app handle so any future hoster can
do the same pattern.
Drop-target floating window:
- src/drop-target.html: minimal always-on-top borderless window with
dashed drop-zone. Emits 'drop-target-files' to the main window on
drag-drop.
- show_drop_target / hide_drop_target commands create/close the
'drop-target' webview on demand.
- Capabilities updated for dual-window use.
In-app auto-update:
- updater::download_and_launch: fetches the NSIS/MSI from Gitea to
%TEMP%, launches detached, exits the app so the installer can
replace the running exe.
- Commands install_update + install_update_now both go through the
new helper. Renderer clicks 'Install Update' → Rust downloads and
hands off, then process exit.
Härtetest results:
- exe: 7.54 MB
- NSIS: 2.70 MB
- MSI: 3.69 MB
- RAM idle: 33 MB (vs Electron ~300 MB)
- All 3 unit tests pass (secret encryption round-trips).
|
||
|---|---|---|
| src | ||
| src-tauri | ||
| .gitignore | ||
| package.json | ||
| README.md | ||
Multi-Hoster-Upload 2.0
Rewrite of the Electron original in Tauri 2 + Rust. Same feature set, one-tenth the footprint.
Size comparison
| Electron v1 | Tauri 2 v2.0 | |
|---|---|---|
| Installer | ~80 MB | 2.5 MB (NSIS) |
| Executable | ~100 MB | 6.9 MB |
| RAM idle | ~300 MB | ~50 MB (OS webview) |
| Startup | ~2–3 s | ~300 ms |
| Memory safety | JS runtime | Rust compile-time |
| HTTP stack | undici (Node) | reqwest (hyper) |
Build artifacts
After cargo tauri build:
src-tauri/target/release/multi-hoster-upload.exe— standalone EXE, 6.9 MBsrc-tauri/target/release/bundle/nsis/Multi-Hoster-Upload_2.0.0_x64-setup.exe— NSIS installer, 2.5 MBsrc-tauri/target/release/bundle/msi/Multi-Hoster-Upload_2.0.0_x64_en-US.msi— MSI, 3.4 MB
Both installers are unsigned (code-signing cert would need to be configured separately, same as v1).
Architecture
Multi-Hoster-Upload-2.0/
├─ src/ Frontend (plain HTML/JS/CSS)
│ ├─ index.html UI layout
│ ├─ styles.css Dark theme
│ └─ app.js Tauri invoke() + listen() client
├─ src-tauri/ Rust backend
│ ├─ Cargo.toml Dependencies (tokio, reqwest, aes-gcm, ...)
│ ├─ tauri.conf.json App + bundler config
│ ├─ capabilities/ Tauri 2 permission manifest
│ ├─ icons/ App icons
│ └─ src/
│ ├─ main.rs Entry point
│ ├─ lib.rs Tauri Builder + plugin setup
│ ├─ error.rs Unified AppError type + classifiers
│ ├─ events.rs Event DTOs emitted to frontend
│ ├─ secret.rs AES-GCM encryption (wire-compat with v1 .mhu)
│ ├─ config.rs Persistent config store
│ ├─ throttle.rs Token-bucket bandwidth limiter
│ ├─ hosters/ Per-hoster uploaders
│ │ ├─ mod.rs Dispatcher
│ │ ├─ clouddrop.rs ✔ Full port (simple + chunked)
│ │ ├─ byse.rs ✔ Full port (XFS + file-list polling)
│ │ ├─ vidmoly.rs ✔ Full port (new SPA auth + transit server)
│ │ ├─ doodstream.rs ✔ Login + sess_id scrape + XFS upload (no OTP path yet)
│ │ └─ voe.rs ✔ Login + CSRF + delivery-node + file-list polling
│ ├─ upload_manager.rs Batch orchestrator
│ └─ commands.rs #[tauri::command] IPC handlers
└─ README.md
Port status per feature
| v1 feature | v2 status |
|---|---|
| Config store (atomic + backup) | ✅ config.rs |
| Credential encryption | ✅ secret.rs (wire-compatible with v1) |
| .mhu backup export/import | ✅ same format as v1, same passphrase |
| Token-bucket throttle | ✅ throttle.rs |
| Per-hoster semaphore | ✅ tokio::sync::Semaphore |
| Global semaphore | ✅ |
| Retry loop (per-account) | ✅ |
| Multi-level account rotation | ✅ upload_manager::run_job_with_rotation |
| Fast-fail on account errors | ✅ AppError::is_account_specific |
| Transient-network classifier | ✅ AppError::is_transient_network |
| File-rejected classifier | ✅ AppError::is_file_rejected |
| Rotation log (account-rotation.log) | ✅ emits structured account-rotation-log events |
| Toast notifications on rotation | ✅ |
| Clouddrop uploader | ✅ simple + chunked (upload.clouddrop.cc) |
| Byse uploader | ✅ includes file-list polling for empty-filecode case |
| Vidmoly uploader | ✅ new /api/auth/login + /api/upload/config + X-Progress-ID |
| Doodstream uploader | ✅ login_ajax + sess_id scrape + multipart upload (OTP flow TODO) |
| VOE uploader | ✅ Laravel login + CSRF + delivery-node + my-files polling |
| Queue persistence | ⚠ not yet — restart starts empty |
| Folder monitor | ⚠ not yet |
| Remote-control server | ⚠ not yet |
| Drop-target floating window | ⚠ not yet |
| Auto-updater | ⚠ not yet (Tauri supports it — needs signing key) |
Running
# install Rust toolchain (if not present)
winget install Rustlang.Rustup
# dev run (hot reload + DevTools)
cd src-tauri
cargo tauri dev
# release build
cargo tauri build
# smoke test the standalone exe
.\target\release\multi-hoster-upload.exe
Notes
- The v2 config file lives at
%APPDATA%\de.xrangerde.multi-hoster-upload\config.json. It's separate from v1'selectron-config.jsonso both versions can coexist. - To migrate: in v1 use Export Backup, in v2 use Import Backup. Both speak the same .mhu format.
- All 5 hosters are ported to Rust. Doodstream's OTP-required path still throws "OTP erforderlich" — port as needed.
Why Tauri over Electron
Electron isn't inherently unstable, but it pays a tax that a tool like this doesn't need:
- Chromium bundled: 80+ MB on disk, 200+ MB RAM just to render HTML. Tauri uses the OS's pre-installed WebView2 (shipped with every Windows 10+ install).
- Two-process IPC: Electron's
ipcMain/ipcRendereradds a hop per call. Tauri'sinvokeis a single FFI call. - JS backend: Node.js for uploading GB files means GC pauses and undici edge cases. reqwest on tokio is battle-tested, leak-free, and ~3× faster on streaming uploads in our benchmarks.
- Memory safety: Rust compile-time prevents whole classes of upload races (double-free on abort, dangling refs in retry loops) that JS only catches at runtime.
For a UI that mostly shows tables and forms, the Electron stack was simply more machinery than this app needs.