Skip to content

feat(settings): choose where extracted stems are stored - #355

Merged
thcp merged 6 commits into
mainfrom
feat/stems-location
Aug 12, 2026
Merged

feat(settings): choose where extracted stems are stored#355
thcp merged 6 commits into
mainfrom
feat/stems-location

Conversation

@thcp

@thcp thcp commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Settings -> General now has a StemData location row: where extracted stems live, how much is there, and a native folder picker to change it.

Closes #354

Why

From the report:

the folder created by the app is automatically created in my Documents folder, which is backed up to my cloud. So I just discovered that my cloud is suddenly 2.5GB bigger.

Documents is the right default -- the library is the user's work, it should be visible in Finder and survive a reinstall -- but it is also the folder most people have syncing to iCloud or OneDrive. Six WAVs plus a video track per song adds up fast, and there was no way to change it short of editing the environment by hand.

The move is the feature

The registry lives inside the stems folder, so writing the preference alone would strand the library and bring the app back empty with the files orphaned. Changing the location moves the files, and the preference is written only after that succeeds -- a failure leaves the app still reading the folder the files are actually in.

Validation is deliberately strict, because this path becomes the root of a directory the app creates and deletes inside:

  • absolute paths only
  • never a folder inside the current one (a directory cannot be moved into itself)
  • an existing folder is accepted only if it is empty or already ours -- merging a stem library into someone's Desktop is not a recoverable mistake
  • refuses while any job is queued or running, since moving files out from under a separation would corrupt it

A move interrupted partway can be re-run: entries already at the target are skipped rather than colliding.

Desktop only

Docker and Unraid get their storage from a mounted volume the operator chose, and it is still the mount on the next start. So the move endpoint 403s outside the desktop shell, and the UI hides the row.

Which of those two decides was worth getting right. The first version sniffed for Tauri in the frontend, putting the judgement in two places; the backend is the thing that knows whether a deployment pins its storage. The read now answers everywhere with an editable flag and only the write is refused. (Probing a 403 to decide -- the second attempt -- logged a failed request in the console every time Settings was opened.)

STEMDECK_JOBS_DIR still wins over everything, which is what makes a pinned deployment immune. For that to hold, the desktop launcher now passes the Documents path as STEMDECK_DEFAULT_JOBS_DIR rather than STEMDECK_JOBS_DIR: a default the setting can override, instead of a pin that would silently defeat it -- the move would happen and the app would come back empty.

Resolution order is now:

  1. STEMDECK_JOBS_DIR -- a deployment that pinned it
  2. settings.json -- what the user chose here
  3. STEMDECK_DEFAULT_JOBS_DIR -- the desktop default
  4. DATA_DIR/jobs, then <repo>/jobs

Testing

23 new tests, 515 total. Most of them are about refusing to destroy a library rather than about moving one.

Verified against a live backend and a real browser: a 1.1 MB library moves, the setting persists, and after a restart the backend reads the new location. Also checked that a Docker-style pin beats the setting, and that a server-mode instance hides the row with no console noise.

Not verified

The native picker and the launcher change need a real desktop build. The Rust compiles and links and adds no new clippy findings, but I have not seen the Finder dialog open, so that is the one thing worth a manual check before this merges.

Also in the report, not addressed here

when I install a newer version, do I have to first delete the older version and its directories?

Worth answering for the reporter, and worth its own issue if the update flow really does need that.

thcp added 2 commits August 11, 2026 21:52
Closes #354

Reported by a user whose cloud storage grew by 2.5 GB without them asking:
the desktop app writes stems to ~/Documents/StemDeck/jobs, and Documents is
the folder most people have syncing to iCloud or OneDrive. A stem library
is six WAVs plus a video track per song, so it adds up fast, and there was
no way to change it short of editing the environment by hand.

Settings -> General now shows where stems live, how much is there, and a
native folder picker to change it.

The move is the feature, not the preference. The registry lives inside the
stems folder, so writing the setting alone would strand the library and
bring the app back empty. Changing the location moves the files, and the
preference is written only after that succeeds -- a failure leaves the app
still reading the folder the files are actually in.

Validation is deliberately strict, because this path becomes the root of a
directory the app creates and deletes inside: absolute paths only, no
folder inside the current one (a directory cannot be moved into itself),
and an existing folder is accepted only when it is empty or already ours.
Merging a stem library into someone's Desktop is not a recoverable mistake.
It also refuses while any job is queued or running, since moving files out
from under a separation would corrupt it.

Desktop only. Docker and Unraid get their storage from a mounted volume
that the operator decides and that is still the mount on the next start, so
both endpoints 403 outside the desktop shell and the UI removes the row
when it is not running under Tauri. STEMDECK_JOBS_DIR keeps winning over
everything, which is what makes a pinned deployment immune.

For that to work the launcher now passes the Documents path as
STEMDECK_DEFAULT_JOBS_DIR rather than STEMDECK_JOBS_DIR: a default the
setting can override, instead of a pin that would silently defeat it.
…move

The row was shown or hidden by sniffing for Tauri in the frontend, which
put the judgement in two places and got it wrong in one: the backend is the
thing that knows a deployment pins its storage.

The read now answers everywhere and carries an `editable` flag; only the
move is refused outside the desktop shell. Probing a 403 to decide, which
was the first attempt, logged a failed request in the console every time
Settings was opened.

Shortens the label to "StemData location". The description explained why
Documents is a bad place for tens of gigabytes, which is true but is three
lines of a settings panel to say something the user already knows by the
time they are looking for this. The one part that mattered -- that it takes
a restart -- is in the message shown after the move, where it is actually
needed.
Comment thread tests/test_stems_location.py Fixed
Comment thread tests/test_stems_location.py Fixed
Comment thread tests/test_stems_location.py Fixed
thcp added 4 commits August 11, 2026 22:18
Flagged by the code-quality bot: the fixtures imported app.main and
app.core.settings as module objects (monkeypatch needs those) and then
pulled names out of the same modules with 'from'. Now module-only.
Four cases in a subprocess, because JOBS_DIR is bound at import time across
the app. The first is the one that matters: an existing desktop user with
no stored choice must keep reading their Documents folder after an update.
Get it wrong and the app opens to an empty library with the stems still on
disk.

Also covers a corrupt settings.json falling back to the default rather than
relocating the library, and an explicit pin (Docker, Unraid) beating a
stray setting in the image.
Two bugs found reviewing this branch, both introduced by it.

JOBS_DIR is bound at import time across the app, so after a move this
process still writes to the folder the files just left. An import accepted
in that window landed in the old folder and vanished on the next start --
stems on disk, in a directory nothing looks at any more. Reproduced against
a live server: the import returned a job id and wrote a fresh registry.json
into the folder that had just been emptied.

Imports are now refused with a 409 that says to restart, from the moment
the move begins. Starting there rather than after it also closes the race
against an import arriving mid-move. The flag is cleared only if the move
fails, since then nothing moved and the app is still right about where the
library is; success is cleared by the restart, which is the point.

Second: a configured folder that no longer exists is no longer honoured.
That means an external disk that is not mounted, and ensure_runtime_dirs
would have happily created the path -- on macOS that puts a real directory
at the mount point on the boot disk and can stop the drive mounting under
its own name later, while the user stares at an empty library. Falling back
to the default leaves everything findable again once the disk is plugged
in.

Also stubs the queue in the endpoint fixture. One of the new tests submits
a real URL, and letting the worker pick it up sent the suite to YouTube:
121 s down to 1.3 s.
…e real folder

Two more from a second read of this branch.

The guard against imports went up after the busy check and after
validate_target, which touches the disk -- it creates the target folder and
lists it. An import accepted during that window would have had its
directory moved out from under it moments later. It now goes up first, and
every early exit reopens it, including the busy 409: leaving it closed
there would block imports until a restart for a move that never happened.

And the panel reported JOBS_DIR, which after a move is the folder the user
just moved away from -- reopening Settings made a successful move look like
it had failed. It now reports where the stems actually are, with the size
read from there, and says a restart is still pending.
@thcp
thcp marked this pull request as ready for review August 12, 2026 10:24
@thcp
thcp merged commit 1e8610b into main Aug 12, 2026
8 checks passed
@thcp
thcp deleted the feat/stems-location branch August 13, 2026 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Choose where extracted stems are stored

1 participant