Skip to content

GET /api/v0/preferences — Field Naming Review #655

Description

@ngosang

@got3nks there is no hurry in making this, is jut to have a clean API before we publish v1. take your time to review field by field.

Review of all 119 fields returned by GET /api/v0/preferences, compared
against the labels the Web UI preferences form renders for the same field
(src/webapi/static/js/views/preferences.js + src/webapi/static/i18n/en.json)
and against the underlying core preference each field maps to
(src/ECSpecialMuleTags.cpp, src/Preferences.h, src/muuli_wdr.cpp).

Goal: are the API field names self-explanatory, or at least sensible? This is
an analysis only — no code was changed.

Naming rules used in this review

Every suggested name in this document follows three rules:

  1. Positive terms only. The name states an affirmative fact, and true
    means that fact holds. No non_*, no_*, not_*, *_disable, and no
    double negatives such as create_files_non_sparse or block_non_friends.
    Where the underlying behaviour is a denial, the name uses an affirmative
    verb for the action itself (filter_all_messages,
    accept_from_friends_only) rather than negating a noun.
  2. Polarity is called out. A handful of positive names can only be reached
    by flipping the boolean's current meaning. Every such case is marked
    ⚠ INVERTS CURRENT SENSE at the point it is proposed, and all of them are
    collected in Section G
    so none can be missed during implementation.
  3. Units in the name for every numeric field, matching the convention the
    payload already uses (max_upload_kbps, min_free_space_mb, kad_reask_ms).

Method

For every field the three names were lined up:

Layer Source
API field GET /api/v0/preferences (src/webapi/Api.cpp, src/webapi/State.h)
Web UI label prefs_field_<category>_<key> in i18n/en.json
Desktop label / core setter src/muuli_wdr.cpp, thePrefs::*

The API↔UI mapping is 1:1: every one of the 119 GET fields is rendered by the
Web UI form, and the UI adds only 6 extra controls that are not GET fields
(4 write-only passwords, the ip2country.update_now trigger, and the derived
read-only udp_server_port).

Verdict at a glance

Category Fields Good Needs a look
general 4 3 1
connection 23 21 2
directories 8 7 1
files 19 9 10
servers 11 5 6
security 13 8 5
message_filter 9 6 3
remote_controls 9 7 2
online_signature 3 2 1
core_tweaks 8 2 6
kademlia 1 1 0
ip2country 11 9 2
Total 119 80 39

The overall shape is good: nested categories mirror the desktop preference
pages, names are snake_case throughout, and most fields read correctly
without documentation. Problems cluster in four places: files, servers,
core_tweaks, and unit-less numeric fields. Only two fields in the whole
payload would need a polarity flip to get a positive name (Section G) — the API
is already largely free of inverted booleans, which is worth preserving.


A. Actively misleading names

The name suggests something different from what the field does, so a client
author can get it wrong without ever seeing a warning.

A1. message_filter.friends — the name reads as the opposite of the behaviour

API message_filter.friends: false
UI label "Filter messages from people not on your friend list"
Core thePrefs::SetMsgOnlyFriends()

Inside a category called message_filter, the field friends reads as "filter
friends". It means the opposite: accept only from friends. Same problem, one
step milder, for message_filter.securethePrefs::SetMsgOnlySecure()
(UI label: "Filter messages from unknown clients").

Suggested (positive, sense unchanged):

Current Suggested Polarity
friends accept_from_friends_only unchanged
secure accept_from_known_clients_only unchanged

Both keep true = "restrict to this group", exactly as today. The affirmative
verb accept avoids the block_non_friends / filter_non_friends double
negative, and matches the core accessor names (MsgOnlyFriends,
MsgOnlySecure) rather than fighting them.

A2. message_filter.all — bare quantifier, no verb

all: false inside message_filter carries no verb at all.
UI label: "Filter all messages".

Suggested: filter_all_messages — polarity unchanged. filter is the
category's own affirmative verb, so this needs no negation.

A3. files.create_normal — name says nothing, and core stores the inverse

API files.create_normal: false
UI label "Create new files non-sparse (allocate real disk blocks)"
Core thePrefs::CreateFilesNormal(val)s_createFilesSparse = !val

"Normal" is meaningless to an API consumer. The setting is about sparse vs
non-sparse file creation, and the core stores the opposite boolean — the API
name inherits a double negative from a legacy wxWidgets accessor.

There is no positive name for the current polarity: today true means
"not sparse", and any faithful name has to say so (create_files_non_sparse,
avoid_sparse_files) — all negations. The only positive term available is the
sparse one:

Suggested: create_sparse_files⚠ INVERTS CURRENT SENSE.
create_sparse_files: true == create_normal: false.

This also makes the backend simpler, because it matches how the core already
stores the value (s_createFilesSparse), removing the ! in
CreateFilesNormal(). The Web UI label would become "Create new files as sparse
files (allocate disk blocks on demand)" with the checkbox default flipped.

A4. security.obfuscation_supported — collides with the capability-flag convention

This payload uses _supported / _available for read-only build
capabilities
the daemon advertises:

  • connection.upnp_available — read-only
  • files.mmap_supported — read-only
  • ip2country.supported — read-only

But security.obfuscation_supported is a writable user preference
thePrefs::SetClientCryptLayerSupported(), desktop label "Support Protocol
Obfuscation". It's the master toggle for the obfuscation group, and it gates
obfuscation_requested / obfuscation_required in the UI.

A client that treats every *_supported field as read-only capability metadata
will silently drop this preference.

Suggested: obfuscation_enabled — polarity unchanged. Keeps _supported /
_available reserved for capabilities, and aligns with the other master
toggles in the payload (ich_enabled, proxy_enabled, upnp_enabled).

A5. files.preview_prio — not a priority

UI label "Try to download first and last chunks first"

Nothing here is a priority value; it's a chunk-ordering strategy for
previewable files.

Suggested: prioritize_first_last_chunks — polarity unchanged.

A6. files.save_sources — under-specifies badly

UI label "Save 10 sources on rare files (< 20 sources)"
Core thePrefs::SetSrcSeedsOn()

save_sources reads as a general "persist my source lists to disk" toggle. It
is specifically about writing source seeds for rare files.

Suggested: save_source_seeds_for_rare_files — polarity unchanged.
(save_source_seeds if the longer form is too much.)

A7. directories.exclude_regex — describes the wrong thing

API directories.exclude_regex: false
UI label "Patterns are regular expressions"
Core thePrefs::ExcludeSharePatternsUseRegex()

The name reads as "exclude regexes" — a second exclusion list alongside
exclude_patterns. It is actually a modifier on exclude_patterns: are those
patterns globs or regexes?

Suggested: exclude_patterns_use_regex — polarity unchanged. Matches the
core accessor name exactly and makes the dependency on exclude_patterns
visible in the name.

A8. security.ipfilter_level — a threshold, not a level of anything

UI label "Filtering level (0-255)"
Core IPFilter.cpp:139: an entry is loaded when it->AccessLevel < accessLevel

The value is a threshold: IP ranges whose access level is below it get
blocked, so a higher number blocks more. "Level" alone tells a client neither
the direction nor what it applies to.

Suggested: ipfilter_block_below_access_level — polarity unchanged
(numeric). Reads affirmatively and encodes the comparison direction.

A9. core_tweaks.max_conn_per_five — "five" of what?

UI label "Max new connections / 5 secs"

The unit is missing from the field name entirely.

Suggested: max_new_connections_per_5s — polarity unchanged.


B. Missing units on numeric fields

The payload is inconsistent about encoding units in the name. Some fields do it
right — max_upload_kbps, max_download_kbps, min_free_space_mb,
kad_reask_ms, source_reask_ms — and then several don't, including one that
sits in the same object as the two _ms fields. None of these involves a
polarity change.

B1. core_tweaks.srv_keepalive_timeout — milliseconds, un-suffixed

API srv_keepalive_timeout: 0
Core GetServerKeepAliveTimeout() returns s_dwServerKeepAliveTimeoutMins * 60000

The value on the wire is milliseconds. Its two siblings in the same object,
kad_reask_ms and source_reask_ms, are also milliseconds and do say so.
This one doesn't.

Suggested: server_keepalive_timeout_ms (also spells out the srv
abbreviation, see Section D).

Related UI note (outside the API-naming scope, but caused by the same gap): the
Web UI applies scale: 60000 to kad_reask_ms and source_reask_ms and shows
them in minutes, but renders srv_keepalive_timeout raw with the label "Server
connection refresh interval (0 = disable)" — so the form shows an unlabelled
millisecond figure where the desktop shows minutes (slider 0–30).

B2. connection.slot_allocation — kB/s per upload slot

UI label "Slot Allocation" (copied verbatim from the desktop)
Core UploadQueue.cpp:306: float kBpsUpPerClient = thePrefs::GetSlotAllocation();

The value is kB/s allocated per upload slot. Neither the field name nor the UI
label says so.

Suggested: upload_slot_kbps. The UI label is equally opaque and would
benefit from "Upload speed per slot (KB/s)".

B3. core_tweaks.filebuffer — bytes

Desktop shows "File Buffer Size: 240000 bytes". Also the only field in the whole
payload that runs two words together without an underscore.

Suggested: file_buffer_bytes.

B4. core_tweaks.ul_queue — a count of clients

Desktop shows "Upload Queue Size: 5000 clients".

Suggested: max_upload_queue_clients (or upload_queue_size).

B5. online_signature.update_frequency — seconds

Desktop: "Update Frequency (Secs)".

Suggested: update_frequency_seconds.

B6. remote_controls.webserver_refresh — seconds

UI label already says "Page refresh time (seconds)".

Suggested: webserver_refresh_seconds.


C. Inconsistent conventions inside one payload

None of these is wrong on its own; together they make the API harder to
predict. No polarity changes in this section.

C1. Enums: two different encodings

Field Encoding
connection.proxy_type int 0..3 (SOCKS5 / SOCKS4 / HTTP / SOCKS4a)
security.can_see_shares int 0..2 (everybody / friends / nobody)
ip2country.source string ("dbip" / "maxmind" / "custom")

ip2country.source is self-describing on the wire; the other two require the
client to hardcode a magic-number table (the Web UI does exactly that, in
PROXY_TYPES and SEE_SHARES). Consistent string enums would remove that.

can_see_shares additionally reads as a yes/no question but holds a 3-state
integer.

Suggested: shared_files_visibility with values "everybody" /
"friends" / "nobody", and proxy_type with "socks5" / "socks4" /
"http" / "socks4a". Value order preserved, so no semantic change.

C2. files.resume_same_cat breaks its own family

Three fields describe the same "what to start when a download finishes"
behaviour, and one is named differently:

API UI label
start_next_paused "Start next paused file when a file completes"
start_next_alphabetical "In alphabetic order"
resume_same_cat "From the same category"

Suggested: start_next_same_category — matches the prefix of the parent it
is gated by, drops the cat abbreviation, and drops "resume", which wrongly
suggests the pause/resume action.

C3. auto_update means three different things

  • servers.auto_update — refresh the server list at startup
  • security.ipfilter_auto_update — refresh the IP filter at startup
  • ip2country.auto_update — refresh the GeoIP database at startup

Two are bare, one is prefixed with its subsystem even though the category
already scopes it. Similarly security.ipfilter_update_url repeats ipfilter
inside security, while servers.update_url and kademlia.update_url rely on
the category. Pick one rule — dropping the redundant prefixes (security.auto_update,
security.update_url) is the smaller and more consistent option, given that
security has no second updatable resource.

C4. Boolean naming: *_enabled vs bare verbs/adjectives

*_enabled form: ich_enabled, proxy_enabled, upnp_enabled,
extended_udp_port_enabled, media_metadata_enabled, mmap_enabled,
webserver_enabled, amuleapi_enabled, message_filter.enabled,
online_signature.enabled, ip2country.enabled.

Bare form: autoconnect, reconnect, remove_dead, share_hidden,
auto_rescan, follow_symlinks, new_paused, endgame, verbose,
check_free_space, use_secident, use_score_system, smart_id_check,
safe_server_connect, paranoid_filtering, use_system_ipfilter.

Most bare ones read fine as English, so this is largely cosmetic. Two read as
truncations rather than sentences and are worth spelling out:

Current Suggested
core_tweaks.verbose verbose_logging
files.endgame endgame_enabled

Two more describe the check but not the effect:

Current UI label Suggested
files.check_free_space "Stop downloads on low free disk space" stop_on_low_disk_space
files.alloc_full_size "Preallocate disk space for new files" preallocate_full_file_size

All four keep their current polarity.

C5. security.ipfilter_filter_lan — stutter

The word "filter" appears twice in one field name. UI label: "Always filter LAN
IPs".

Suggested: ipfilter_include_lan_ips — polarity unchanged.

C6. remote_controls: prefixes doing the work of nesting

All nine fields are prefixed webserver_ or amuleapi_ because two unrelated
subsystems share one flat object:

remote_controls.webserver_enabled, webserver_port, webserver_use_gzip,
                webserver_refresh, webserver_template, webserver_guest_enabled,
                amuleapi_enabled, amuleapi_port, amuleapi_bind

Nesting (remote_controls.webserver.{...}, remote_controls.amuleapi.{...})
would drop the prefixes and match how the rest of the payload uses categories.
This is a breaking structural change, so it is worth noting rather than doing.

C7. amuleapi_bind vs bind_address

connection.bind_address and connection.bind_interface spell out what is
being bound. remote_controls.amuleapi_bind is a bare verb holding an IP
(UI label: "Listening IP").

Suggested: amuleapi_bind_address.


D. Abbreviations that don't need to be abbreviations

All polarity-unchanged.

Current Meaning Suggested
files.new_auto_dl_prio new downloads get auto priority new_downloads_auto_priority
files.new_auto_ul_prio new shared files get auto priority — not uploads of your downloads (UI: "Add new shared files with auto priority") new_shared_files_auto_priority
files.aich_trust "Advanced I.C.H. trusts every hash" aich_trust_every_hash
files.new_paused "Add files to download in pause mode" add_new_downloads_paused
files.resume_same_cat see C2 start_next_same_category
servers.autoconn_static_only "Autoconnect to servers in static list only" autoconnect_static_servers_only
servers.manual_high_prio manually added servers → High priority manual_servers_high_priority
servers.dead_server_retries retries before a server counts as dead fine as-is
core_tweaks.kad_max_searches GetKadMaxSourceSearches(), UI: "Concurrent Kad source lookups" kad_max_source_searches
core_tweaks.srv_keepalive_timeout see B1 server_keepalive_timeout_ms
core_tweaks.ul_queue see B4 max_upload_queue_clients

prio / dl / ul / cat / srv / autoconn are the only abbreviations in
the payload; everything else spells words out, so they stand out.


E. Legacy core names leaking through, where the UI already says something better

Defensible (they match thePrefs:: / EC tag names 1:1, which makes the backend
easy to audit) but they contradict the label the same field is shown with. All
polarity-unchanged.

Current UI label / desktop label Suggested
servers.use_score_system "Use priority system" Core is s_scorsystem / SetScoreSystem. The desktop dropped the word "score" years ago; the API kept it. → use_priority_system
servers.add_from_server "Update server list when connecting to a server" "Add" what, from where? → update_list_from_server
servers.add_from_client "Update server list when a client connects" update_list_from_client
servers.safe_server_connect "Safe connect" safe_connect (drops the redundant server inside servers)
general.host_name "Host name" thePrefs::GetYourHostname() — the local hostname this client advertises, read-only. Ambiguous next to proxy_host and amuleapi_bind. → local_host_name or advertised_host_name
files.create_normal see A3 see A3 — the one case that needs a polarity flip
files.save_sources see A6 save_source_seeds_for_rare_files
security.paranoid_filtering "Paranoid handling of non-matching IPs" Jargon, but the desktop uses the same word and the tooltip explains it. Leave as-is, or reject_spoofed_source_ips to say what it does.

F. Names that are already good

Worth recording so a future rename pass doesn't churn them:

  • Units in the name: max_upload_kbps, max_download_kbps,
    min_free_space_mb, kad_reask_ms, source_reask_ms.
  • Ports: tcp_port, udp_port, proxy_port, upnp_tcp_port,
    webserver_port, amuleapi_port — consistent and unambiguous.
  • directories paths: incoming, temp, shared, share_hidden,
    auto_rescan, follow_symlinks, exclude_patterns. Clean, and
    category-scoped without redundant prefixes.
  • Capability flags (read-only, daemon-advertised): upnp_available,
    files.mmap_supported, ip2country.supported. Consistent — which is exactly
    why A4 is a problem.
  • ip2country status block: source / loaded_source (requested vs
    actually loaded) is a genuinely good pair, and db_path / db_loaded read
    correctly. Minor nits only: downloadingdownload_in_progress,
    last_resultlast_update_result.
  • Obfuscation triple: obfuscation_requested / obfuscation_required is a
    precise distinction (prefer vs mandate), stated positively. Only the third
    member, obfuscation_supported, is misnamed.
  • Already-positive inversion of an EC negative:
    connection.extended_udp_port_enabled deliberately inverts
    EC_TAG_CONN_UDP_DISABLE so the API has no double negative. This is precisely
    the pattern the rest of this document asks for, it is already documented in
    State.h, and it is the precedent for the create_sparse_files flip in A3.
  • Write-only passwords (proxy_password, webserver_password,
    webserver_guest_password, amuleapi_password) are accepted on PATCH and
    correctly absent from GET.

G. Polarity changes: fields whose sense would flip

Out of 39 flagged fields, exactly one boolean cannot be given a positive
name without inverting its current meaning. Everything else in this document is
a pure rename.

Current field Current true means Suggested field New true means Equivalence
files.create_normal files are created non-sparse (real blocks allocated up front) create_sparse_files files are created sparse (blocks allocated on demand) create_sparse_files == !create_normal

Implementation consequences of that single flip:

  • Wire: create_normal: false (today's default) becomes
    create_sparse_files: true. A client that blindly renames the key without
    flipping the value gets the opposite behaviour — this is the one place where a
    mechanical find-and-replace is wrong.
  • Backend: it removes a negation rather than adding one. The core already
    stores s_createFilesSparse, and thePrefs::CreateFilesNormal(val) does
    s_createFilesSparse = !val. Emitting create_sparse_files lets the webapi
    read and write the stored value directly.
  • Web UI: prefs_field_files_create_normal ("Create new files non-sparse
    (allocate real disk blocks)") becomes "Create new files as sparse files
    (allocate disk blocks on demand)", and the checkbox's meaning flips. Because
    the Web UI reads the same key it writes, no extra inversion logic is needed
    there — only the label and its Spanish translation change.
  • Transition: if both keys are emitted during a deprecation window, they
    must carry opposite values. That is the one asymmetry versus every other
    rename in this document, and a reason to do this field on its own commit
    rather than inside a bulk rename.

Two near-misses that are not polarity changes, recorded so they don't get
mistaken for one:

  • security.obfuscation_supportedobfuscation_enabled (A4). Same value,
    same meaning; only the misleading _supported suffix goes.
  • message_filter.friendsaccept_from_friends_only (A1). true still means
    "restrict to friends". The name stops reading as an inversion — the value
    never was one.

Recommended priority, if a rename pass ever happens

Renaming API fields is breaking, so this is ordered by "how likely is this to
cause a wrong integration", not by how ugly the name is.

Tier 1 — semantic hazards (a client can get these wrong silently):

  1. security.obfuscation_supportedobfuscation_enabled (A4)
  2. message_filter.friends / .secure / .allaccept_from_friends_only / accept_from_known_clients_only / filter_all_messages (A1, A2)
  3. core_tweaks.srv_keepalive_timeoutserver_keepalive_timeout_ms (B1)
  4. directories.exclude_regexexclude_patterns_use_regex (A7)
  5. files.create_normalcreate_sparse_filesown commit, polarity flip (A3, G)

Tier 2 — missing units and under-specified names:
6. connection.slot_allocationupload_slot_kbps (B2)
7. core_tweaks.max_conn_per_fivemax_new_connections_per_5s (A9)
8. core_tweaks.filebuffer / ul_queuefile_buffer_bytes / max_upload_queue_clients (B3, B4)
9. files.save_sources, files.preview_priosave_source_seeds_for_rare_files, prioritize_first_last_chunks (A6, A5)
10. security.ipfilter_levelipfilter_block_below_access_level (A8)
11. online_signature.update_frequency, remote_controls.webserver_refresh*_seconds (B5, B6)

Tier 3 — consistency and abbreviations (cosmetic, batch them):
12. Enum encodings: proxy_type, can_see_shares → string enums (C1)
13. files.resume_same_catstart_next_same_category (C2)
14. The dl / ul / prio / cat / srv / autoconn abbreviations (D)
15. servers.use_score_system, add_from_server, add_from_client (E)
16. security.ipfilter_filter_lan, core_tweaks.verbose, files.endgame,
files.check_free_space, files.alloc_full_size (C4, C5)

Not recommended: the remote_controls re-nesting (C6) — real improvement,
but a much larger break than the rest for a purely structural gain.

If backwards compatibility matters, the usual escape hatch applies: emit both
the old and the new key on GET for one release, accept both on PATCH, and
drop the old one after. That is cheap here because every field already goes
through a single emit site in src/webapi/Api.cpp and a single PrefTake* call
on the write path. The one exception is files.create_normal — during its
window the two keys must carry opposite values (Section G).

Web UI labels worth revisiting at the same time

Two labels are as opaque as the API fields behind them, since they were copied
verbatim from the desktop dialog:

  • prefs_field_connection_slot_allocation = "Slot Allocation" → "Upload speed
    per slot (KB/s)"
  • prefs_field_core_tweaks_srv_keepalive_timeout = "Server connection refresh
    interval (0 = disable)" → needs a unit, and the field needs scale: 60000 to
    show minutes like the desktop does (see B1)

And one label changes as a consequence of the polarity flip:

  • prefs_field_files_create_normal = "Create new files non-sparse (allocate
    real disk blocks)" → "Create new files as sparse files (allocate disk blocks
    on demand)", with the checkbox default inverted (see G)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions