beetbox / beetbox/beets

Proposal: better import mode configuration

Open
#6,629 2 comments 6 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
15.7k
Forks
2.1k
Avg merge
4d 21h
Merged PRs (30d)
31

Description

This issue aims to discuss the improvement of the UX for configuring file operations during beets import.

# Background

Beets supports many ways of importing files:

- use the files in their current location (in place)
- copy
- move
- link (symlink)
- hardlink
- reflink

To configure them, we have as many [boolean config options](https://beets.readthedocs.io/en/stable/reference/config.html#importer-options).
Some of these modes can be overridden via the CLI with [flags](https://beets.readthedocs.io/en/stable/reference/cli.html#import).

# Problems

While modes are mutually exclusive (it does not make sense to both copy and move, or both move and hardlink), we allow enabling multiple of them at the same time.
This leads to the following issues:

## Unclear behavior

Beets does not complain when multiple modes are enabled at the same time. It simply picks one using a [priority system](https://github.com/beetbox/beets/blob/master/beets/importer/session.py#L125) without telling the user. This can lead users to believe one mode is active while beets silently applies another. This behavior is partially documented, but not for all options. For example:

> **reflink**: [...] The option is ignored if move is enabled (i.e., beets can move or copy files but it doesn’t make sense to do both).

This is not quite true, it will be also ignored if link or hardlink are enabled.

Figuring out what beets will do requires factoring in the defaults and priorities. This is even more confusing when using CLI flags as one needs to know what exactly is in their config when running the command and factor that in as well.

## Configuration bloat

Each of these modes have their own config option, making the config system and the documentation more complex and verbose.

This complexity is even more pronounced in the CLI where we need both an ON and OFF flag for each of the modes we want to support.

## In place, the forgotten child

When all modes are turned off, beets just uses the files where they are, without copying, moving, or anything.

Users may assume that one of the options needs to be enabled and not realize that turning them all off enables another, "hidden" mode: use the original files in place.

# Proposal

Replace current boolean flag with a single enum:

- `inplace`
- `copy` (default)
- `move`
- `link`
- `hardlink`
- `reflink`

Exactly one mode may be selected.

### Benefits

- **clarity**: only one mode can be configured at a time, CLI overrides are way simpler. The precedence system is removed entirely.
- **discoverability**: users can now see at a glance in the documentation or in the CLI help what are their options.
- **validation**: we now have a clear set of valid values, everything else is an error.
- **documentation**: documentation is easier to write now that everything is a single option
- **CLI ergonomics**: this cuts the number of CLI flags from today's 4 (potentially 10 if all modes were available as CLI flags with the current implementation) to just one.
- **future extensibility**: adding another mode means just adding a new enum value.

### Config

```yaml
import:
mode: move
```

### CLI

```shell
beet import --mode move
beet import --mode inplace

```

Sadly, `-m` and `-M` are already used for current flags and cannot be used as a shorthand for the new `--mode`. We may not need one as this is a relatively advanced and niche use case.

# Deployment plan

## Phase 1

This first phase is about introducing this new mode of operation without breaking current user setups. Plugins or external tooling relying on legacy boolean settings would continue to function during Phase 1.

### Code

New config option and flag are introduced.

The following handling is applied:

| | old setting(s) set | old setting(s) unset |
| -------------------- | ------------------------------------------------ | -------------------- |
| **new `mode` set** | error **\*** | use new `mode` |
| **new `mode` unset** | compute mode from old settings, warning **\*\*** | `mode` = copy |

- **error\***: Mixing old and new configuration styles is rejected to avoid ambiguous behavior and simplify migration.
- **warning\*\***: Compute the `mode` value equivalent to the user config and display a warning indicating that the settings they are using are deprecated and that they should switch to `mode: $computed_value`

Users are allowed to override old settings with the new `--mode` flag.

### Documentation

New config and flag are documented. Previous settings and flags are marked as deprecated and redirect to their new replacements.

## Phase 2

Removal of old settings and flags.

Contributor guide

Open the contributing guide

Research direction

Read beets/importer/session.py around line 125 and review the importer configuration and import CLI option documentation linked in the issue. Confirm how legacy boolean settings and flags are currently combined, then define the migration behavior and tests needed for both deployment phases.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
cli, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.