Phase 5: Replace all Box<dyn Error> return types with anyhow::Result<T>
throughout the codebase. Replace string-based Err("msg".into()) and
format!().into() patterns with bail!() and anyhow!() macros. Fix
dirs::home_dir().unwrap() in settings.rs to use a fallback path instead
of panicking when HOME is unset. Remove stray use std::error::Error
imports no longer needed.
Phase 6: Add From<&User> for CacheUser in models/user.rs and
From<&Laboratory>/From<&Laboratories> for CacheLaboratory/CacheLabsWrapper
in models/laboratory.rs. Simplify commands/login.rs to use .into()
conversions, removing the redundant to_cache_user() and to_cache_labs()
helper functions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add scripts/build-release-linux.sh for building musl-linked release
archives (x86_64 and aarch64) on Linux. Uses `cross` for cross-
compilation so no host toolchain beyond Docker/Podman is required.
Also add mdrs-*.tar.gz and mdrs-*.zip to .gitignore to prevent
generated release archives from being tracked.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The access token obtained at command startup could expire during a long
transfer session (e.g. uploading thousands of files or large files),
causing subsequent requests to fail with 401 Unauthorized.
Root cause: load_cache_with_token_refresh was called only once, and the
resulting MDRSConnection — including its now-stale token — was shared
across all parallel tasks via Arc. There was no mechanism to update the
token in the shared instance after creation.
Fix:
- Add MDRSConnection::with_token(&self, token) that creates a new
connection struct reusing the caller's HTTP client (cheap Arc clone,
shares the connection pool) but carrying a fresh Bearer token.
- In upload.rs and download.rs, call load_cache_with_token_refresh
inside each tokio::spawn task body, then create a task-local
connection via conn.with_token(fresh_token) before transferring the
file. The shared reqwest::Client (connection pool) is preserved.
cp.rs is not changed: it uses only short server-side API calls with no
parallel tasks, so token expiry during a cp operation is negligible.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous implementation had two correctness issues:
1. flock on .tmp was ineffective for cross-process exclusion.
After fs::rename(), the .tmp inode disappears. A second process
opening .tmp gets a brand-new inode, so both processes hold flocks
on different inodes simultaneously — no mutual exclusion occurs.
2. The critical section was too narrow. The in-process tokio::Mutex
only serializes tasks within the same process. Two separate mdrs
processes could both read the cache, both decide a refresh was
needed, and both call the token-refresh endpoint before either had
written the new token back — risking double-refresh and potential
failures on servers that use refresh-token rotation.
Fix: introduce a dedicated `cache/{remote}.lock` file as the cross-
process advisory lock target. The lock file is never renamed, so its
inode remains stable for the entire critical section. The flock now
wraps the complete read-check-refresh-write cycle in
load_cache_with_token_refresh(), and the redundant flock on .tmp in
refresh_and_persist() is removed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- api/files.rs: NFC-normalize filename before sending to server in
upload_file(). On macOS, local filenames may be NFD-encoded, which
would cause the server to store them as NFD instead of NFC.
- commands/download.rs: replace direct to_lowercase() subfolder
comparison with find_subfolder_by_name() helper, which already
applies NFC normalization on both sides.
- commands/cp.rs, mv.rs: apply nfc() to s_basename (source path
component from user input) for consistency with d_basename, so
the no-op identity check and find_*() calls use normalized strings.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
On Windows, std::fs::canonicalize returns an extended-length path
with the \?\ prefix, which does not support forward slashes.
Using format!("{}/{}", ...) to join paths then caused os error 123
(ERROR_INVALID_NAME).
Replace all string-based path concatenation with PathBuf::join so
that the OS-appropriate separator is used on every platform.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CI does not have macOS or Windows runners, so provide scripts to
build and optionally upload release archives locally.
- scripts/build-release-macos.sh — builds x86_64 and aarch64-apple-darwin
- scripts/build-release-windows.ps1 — builds x86_64-pc-windows-msvc
- scripts/.env.example — template for Gitea credentials
Both scripts read GITEA_TOKEN, GITEA_SERVER_URL, and GITEA_REPOSITORY
from the environment or from scripts/.env (which is gitignored).
Upload to Gitea is skipped when GITEA_TOKEN is not set.
Use curl.exe for multipart upload in the PowerShell script to support
Windows PowerShell 5.1 (Invoke-RestMethod -Form requires PS 6.1+).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
cross runs builds inside a Docker container, so the host-installed
Rust toolchain is not visible. Replace with cargo-zigbuild which
uses Zig as a cross-linker and runs directly on the host.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The act-runner environment does not have rustup pre-installed.
Install it using the official sh.rustup.rs script and append
~/.cargo/bin to GITHUB_PATH so subsequent steps can find the tools.
Node.js-based actions (actions/checkout, gitea-release-action) run
on the host runner where Node.js is available, so no container image
is needed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace manual curl-based release creation and asset upload with
akkuman/gitea-release-action@v1. Each build job now independently
creates/updates the release and uploads its asset in a single step.
Also remove macOS and Windows jobs as those runners are not available.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fetch latest release from Gitea API using existing reqwest client
- Match release asset by BUILD_TARGET triple (supports .tar.gz and .zip)
- Compare versions; show confirmation prompt (skippable with -y/--yes)
- Download archive, extract binary, atomically replace self via self-replace
- Support private repositories via GITEA_TOKEN environment variable
- Expose BUILD_TARGET in build.rs for compile-time target triple detection
- Add .gitea/workflows/release.yml for multi-platform release builds on tag push
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>