Skip to content

feat(prefs): add a GUI control for the sparse part-file setting (/eMule/CreateSparseFiles) - #662

Merged
got3nks merged 5 commits into
amule-org:masterfrom
LSalami:add-sparse-files-prefs-ui
Jul 28, 2026
Merged

feat(prefs): add a GUI control for the sparse part-file setting (/eMule/CreateSparseFiles)#662
got3nks merged 5 commits into
amule-org:masterfrom
LSalami:add-sparse-files-prefs-ui

Conversation

@LSalami

@LSalami LSalami commented Jul 28, 2026

Copy link
Copy Markdown

Closes #653.

/eMule/CreateSparseFiles was a functional, EC-wired preference — already exposed via the Web UI as files.create_normal — but had no control in either amule or amuleGUI. The only way to change it was hand-editing amule.conf.

What changed

  • Added a checkbox on the Files preferences page, right next to "Preallocate disk space for new files".
  • Moved the pref from the untracked s_MiscList into the same NewCfgItem(IDC_*, Cfg_Bool(...)) wiring every other GUI-behavior checkbox on that page already uses.

No bespoke TransferToWindow/OnOk logic was needed — PrefsUnifiedDlg.cpp is shared between amule and amuleGUI, so the one control works in remote mode too, and EC round-tripping was already in place (ECSpecialMuleTags.cpp already reads/writes this pref via the existing CreateFilesSparse()/CreateFilesNormal() accessors).

Polarity

Kept the checkbox in the stored key's native "sparse" sense (checked = sparse = today's default), since Cfg_Bool binds 1:1 with no invert option. The tooltip spells out what unchecking it does, rather than introducing a polarity flip the binding class doesn't support.

Testing

Built and ran both amule and amuleGUI locally. Confirmed at the config-file level:

  • A fresh config writes CreateSparseFiles=1, matching the existing default.
  • A hand-edited CreateSparseFiles=0 loads back unchanged on the next launch (not silently reset to the default).

Full GUI toggle-and-save wasn't exercisable here — macOS Accessibility automation for this dialog hit the same limitations documented in #180. This is the exact same generic Cfg_Bool/NewCfgItem binding already proven correct for every sibling checkbox on the page, with no per-control logic of its own, so I'm confident in the mechanism; happy to have this double-checked in review/CI.

po/ regenerated as the final step — the only new msgid is the checkbox's own label/tooltip.

@LSalami

LSalami commented Jul 28, 2026

Copy link
Copy Markdown
Author

Visually confirmed locally (macOS build): the checkbox appears on the Files preferences page right below "Preallocate disk space for new files", as intended.

@LSalami
LSalami force-pushed the add-sparse-files-prefs-ui branch from 758dc43 to f49a2f2 Compare July 28, 2026 08:23
@got3nks

got3nks commented Jul 28, 2026

Copy link
Copy Markdown

Thanks @LSalami — the wiring is clean and the s_MiscListNewCfgItem move is exactly right. I checked the one part that looked risky, moving it out of s_MiscList: NewCfgItem is defined for daemon builds too (auto-incrementing key) and SaveAllItems iterates both lists, so amuled persistence is unaffected. Polarity (checked = sparse, matching the stored key) is the right call.

Three things before merge.

1. ID collision. #665 landed since you opened this and took IDC_AMULEAPI_GUEST_ENABLED = 10489 — the same ID as IDC_CREATEFILESSPARSE. Since NewCfgItem is s_CfgList[ID] = …, the two would overwrite each other in the map and one preference would silently stop binding. Next free is 10492 (10490/10491 also went to #665). The // 10488 reserved by #643 … not yet on master comment is stale now too — #643 merged.

2. The tooltip is inverted. It says a sparse file "reserves its full size on disk immediately" — that's preallocation, i.e. the checkbox directly above. A sparse file reserves no disk space; it's created at full logical length (SetEndOfFile) while physical allocation grows as data lands. "Turn this off to grow the file only as data arrives" is backwards too: with sparse off on NTFS, writing at a high offset makes the filesystem allocate and zero-fill up to it, consuming more space sooner. Worth fixing before the regen, since a wrong msgid gets faithfully translated into 40 languages.

3. The setting is a no-op outside Windows, and the checkbox doesn't say so. On POSIX both branches are the same call — CFile::Create(name, true) in PlatformSpecific.cpp (under a comment reading "non Windows systems don't need all this") versus m_hpartfile.Create(m_PartPath, true) in PartFile.cpp — and a part file becomes sparse anyway once chunks land at offsets. Only the Windows path does real work, so on Linux/macOS this is a visible control that does nothing. (#653's table has the same gap: its first two rows are identical off Windows.)

I'd document it rather than gate it: what matters is the core's platform, which amuleGUI can't know without a new EC capability tag, so a compile-time #ifdef would be wrong in both directions — hiding a meaningful control when a Linux amuleGUI drives a Windows core, showing an inert one in reverse.

Points 2 and 3 fold into one tooltip rewrite:

Sparse part files only occupy disk space for the parts already downloaded, so free space is used up gradually as the file fills in. Turn this off to use an ordinary file instead - useful where sparse files are unsupported or slow, or where backup/de-duplication tools handle them badly. Applies only when the core runs on Windows; on Linux and macOS part files are sparse anyway and this setting has no effect.

Optional, not a blocker: additionally hide the checkbox under #if !defined(__WINDOWS__) && !defined(CLIENT_GUI) — monolithic non-Windows is the one configuration where inertness is a compile-time certainty.

Mechanical: #665 touched both the catalogs and the ID space, so this now conflicts in po/ and muuli_wdr.h. Rebase, take the new ID, fix the tooltip, and re-run scripts/update-po.sh last so the corrected string is what lands in the catalogs.

LSalami added a commit to LSalami/amule that referenced this pull request Jul 28, 2026
…-only effect

got3nks's review on amule-org#662, both real issues:

1. The tooltip had it backwards. A sparse file reserves *no* physical
   disk space up front -- it's created at full logical length but grows
   physically as data lands, the opposite of what the old wording said
   ("reserves its full size... immediately"). Rewritten with the exact
   corrected text from the review.

2. The setting is a no-op outside Windows: PlatformSpecific.cpp and
   PartFile.cpp both call CFile::Create(name, true) on POSIX regardless
   of this preference, and part files end up sparse anyway once chunks
   land at offsets. Documented in the tooltip and a code comment rather
   than gating the checkbox behind __WINDOWS__ -- a remote amuleGUI has
   no EC capability tag to know what platform the core it's driving
   runs on, so a compile-time gate would be wrong in both directions.

The ID collision with amule-org#665 (IDC_AMULEAPI_GUEST_ENABLED etc. took
10489-10491, the same range IDC_CREATEFILESSPARSE was using) was
already resolved during the rebase -- moved to 10492.

po/ regenerated last, after the tooltip fix, so the corrected string
(not the wrong one) is what lands in the catalogs.

Verified: built and ran both amule and amuleGUI, full unit test suite
passing (28/28, including the new CredentialsTest from amule-org#665).
@LSalami
LSalami force-pushed the add-sparse-files-prefs-ui branch from f49a2f2 to e7fad85 Compare July 28, 2026 10:21
@LSalami

LSalami commented Jul 28, 2026

Copy link
Copy Markdown
Author

All three fixed in e7fad85 (the ID collision got resolved as part of the rebase itself, moved to 10492):

  1. ID collision — resolved during the rebase, IDC_CREATEFILESSPARSE is now 10492.
  2. Tooltip corrected — used your exact wording. Good catch, I had the sparse-file mechanics backwards.
  3. Windows-only effect documented — tooltip note plus a code comment explaining the PlatformSpecific.cpp/PartFile.cpp identical-on-POSIX gap. Agreed on documenting over gating for the reason you gave (amuleGUI can't know the core's platform).

po/ regenerated last as requested, so the corrected string is what's in the catalogs.

Verified: built and ran both amule and amuleGUI, full unit test suite passing (28/28, including the new CredentialsTest from #665).

@got3nks got3nks left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified all three points on the rebased head — ID is 10492 with no collision (and the stale #643 comment is gone), the tooltip matches verbatim, and the catalogs carry the corrected msgid with zero trace of the old inverted wording, so the regen-last ordering held. Thanks.

Two things and it's ready:

1. Add the non-Windows hide (the item I'd previously marked optional). One caveat on how: don't wrap the wxCheckBox creation in muuli_wdr.cpp in the #if. The NewCfgItem(IDC_CREATEFILESSPARSE, …) binding would then have no widget, Cfg_Tmpl::ConnectToWidget returns false, and PrefsUnifiedDlg logs "Failed to connect Cfg to widget…" plus "Failed to transfer data from Cfg to Widget…" every time Preferences opens.

Keep the control and the binding unconditional, and just hide it — same idiom already used a few lines up in PrefsUnifiedDlg.cpp:

#if !defined(__WINDOWS__) && !defined(CLIENT_GUI)
	// Monolithic non-Windows: this build is its own core and the setting is
	// a no-op on POSIX, so hide it. Still registered, so the value keeps
	// round-tripping through the config and EC.
	if (wxWindow *sparse = FindWindow(IDC_CREATEFILESSPARSE)) {
		sparse->Show(false);
	}
#endif

2. Rebase and re-run scripts/update-po.sh last — it's conflicting again after #665, and CI can't complete while the branch is dirty.

LSalami added 5 commits July 28, 2026 13:09
/eMule/CreateSparseFiles was a functional, EC-wired preference (already
exposed via the Web UI as files.create_normal) but had no control in
either amule or amuleGUI -- users could only change it by hand-editing
amule.conf (amule-org#653).

Adds a checkbox on the Files preferences page, next to "Preallocate
disk space for new files", and moves the pref from the untracked
s_MiscList into the same NewCfgItem(IDC_*, Cfg_Bool(...)) wiring every
other GUI-behavior checkbox on that page already uses -- no bespoke
TransferToWindow/OnOk logic needed, PrefsUnifiedDlg.cpp is shared
between amule and amuleGUI so the same one control works in remote mode
too, and EC round-tripping was already in place (ECSpecialMuleTags.cpp
already reads/writes this pref via the CreateFilesSparse()/
CreateFilesNormal() accessors).

Kept the checkbox in the stored key's native "sparse" sense (checked =
sparse = today's default) since Cfg_Bool binds 1:1 with no invert
option; the tooltip spells out what turning it off does rather than
introducing a polarity flip Cfg_Bool doesn't support.

Verified for real: built and ran both amule and amuleGUI, confirmed a
fresh config writes CreateSparseFiles=1 (matching the existing
default), and that a hand-edited CreateSparseFiles=0 loads back
unchanged (not silently reset to the default) -- full GUI toggle-and-
save wasn't exercisable here due to the same macOS Accessibility
limitations documented in amule-org#180, but this is the exact same generic
Cfg_Bool/NewCfgItem binding already proven correct for every sibling
checkbox on the page, with no per-control logic of its own.

po/ regenerated as the final step; the only new msgid is the checkbox
label/tooltip themselves.
Mechanical rebase to resolve po/ conflicts against master's amule-org#642/amule-org#643
-- no source changes here. Also resolved a real conflict in
muuli_wdr.h: amule-org#643's IDC_SEARCHHISTORYENABLED (10488) landed on master,
which this branch had already anticipated and reserved 10489 for.
…-only effect

got3nks's review on amule-org#662, both real issues:

1. The tooltip had it backwards. A sparse file reserves *no* physical
   disk space up front -- it's created at full logical length but grows
   physically as data lands, the opposite of what the old wording said
   ("reserves its full size... immediately"). Rewritten with the exact
   corrected text from the review.

2. The setting is a no-op outside Windows: PlatformSpecific.cpp and
   PartFile.cpp both call CFile::Create(name, true) on POSIX regardless
   of this preference, and part files end up sparse anyway once chunks
   land at offsets. Documented in the tooltip and a code comment rather
   than gating the checkbox behind __WINDOWS__ -- a remote amuleGUI has
   no EC capability tag to know what platform the core it's driving
   runs on, so a compile-time gate would be wrong in both directions.

The ID collision with amule-org#665 (IDC_AMULEAPI_GUEST_ENABLED etc. took
10489-10491, the same range IDC_CREATEFILESSPARSE was using) was
already resolved during the rebase -- moved to 10492.

po/ regenerated last, after the tooltip fix, so the corrected string
(not the wrong one) is what lands in the catalogs.

Verified: built and ran both amule and amuleGUI, full unit test suite
passing (28/28, including the new CredentialsTest from amule-org#665).
…builds

got3nks's review on amule-org#662: the setting is a no-op outside Windows, so
hide it there -- but as a post-creation Show(false) in PrefsUnifiedDlg's
ctor, not by skipping wxCheckBox creation in muuli_wdr.cpp. Gating the
creation would leave NewCfgItem(IDC_CREATEFILESSPARSE, ...) with no
widget to connect to, and Cfg_Tmpl::ConnectToWidget would fail silently
every time Preferences opens (logging "Failed to connect Cfg to
widget..." / "Failed to transfer data from Cfg to Widget...").

Same #if !defined(__WINDOWS__) && !defined(CLIENT_GUI) / FindWindow +
Show(false) idiom already used a few lines up for the CLIENT_GUI-only
share-preview hide.
Mechanical rebase to resolve po/ conflicts against master's amule-org#665/amule-org#667
-- no source changes here.
@LSalami
LSalami force-pushed the add-sparse-files-prefs-ui branch from e7fad85 to 247fc7d Compare July 28, 2026 11:13
@LSalami

LSalami commented Jul 28, 2026

Copy link
Copy Markdown
Author

Done in 5483fd7 — hide-after-creation, exactly as you described, using the same idiom as the CLIENT_GUI share-preview hide a few lines up. Widget stays created and bound unconditionally, so Cfg_Tmpl::ConnectToWidget always has something to connect to.

Rebased onto current master and regenerated po/ last (247fc7d) — zero msgid changes this round, this fix is code-only.

Verified: built and ran both amule and amuleGUI, full unit test suite passing (28/28).

@got3nks got3nks left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hide-after-creation is exactly right — the widget stays bound so the Cfg binding never fails, and it sits before the Fit() so the layout collapses cleanly. All four points verified. Merging.

@got3nks
got3nks merged commit 27c5754 into amule-org:master Jul 28, 2026
13 checks passed
LSalami added a commit to LSalami/amule that referenced this pull request Jul 28, 2026
Mechanical rebase to resolve conflicts against master's amule-org#642/amule-org#643 --
no source changes here. Resolved a real conflict in amuleDlg.cpp's
event table: amule-org#642 added the Alt+<letter> EVT_MENU block right where
this branch removed the EVT_TOOL(ID_BUTTONCONNECT, ...) line; kept
amule-org#642's block, dropped the connect-button line as intended.

Regenerated again via scripts/update-po.sh after later rebasing onto
master's amule-org#671 (Preferences sidebar) and amule-org#665/amule-org#662 (amuleapi
credentials, sparse part-file setting), which had added strings this
branch's catalogs didn't have yet.
LSalami added a commit to LSalami/amule that referenced this pull request Jul 28, 2026
Mechanical rebase to resolve conflicts against master's amule-org#642/amule-org#643 --
no source changes here. Resolved a real conflict in amuleDlg.cpp's
event table: amule-org#642 added the Alt+<letter> EVT_MENU block right where
this branch removed the EVT_TOOL(ID_BUTTONCONNECT, ...) line; kept
amule-org#642's block, dropped the connect-button line as intended.

Regenerated again via scripts/update-po.sh after later rebasing onto
master's amule-org#671 (Preferences sidebar) and amule-org#665/amule-org#662 (amuleapi
credentials, sparse part-file setting), which had added strings this
branch's catalogs didn't have yet.
got3nks pushed a commit that referenced this pull request Jul 28, 2026
* feat(gui): remove the global Connect/Disconnect toolbar button

Closes #402. Per-network controls already exist (Kad pane's Start/Stop,
Servers pane's ED2K connect/disconnect), the connection state is
already shown in the status bar, and after discussing relocating it
into the Networks view the agreed call (got3nks + ngosang) was simply
to remove it -- mirrors what #585 already did on the Web UI side.

Removed the toolbar tool, its three skin icons (Toolbar_Connect/
Disconnect/Connecting) and their bitmap wiring, the button-update block
in ShowConnectionState(), and the two EnableTool(ID_BUTTONCONNECT, ...)
call sites.

CamuleDlg::OnBnConnect() itself stays -- CMuleTrayIcon::DoConnectDisconnect()
still calls it for the tray icon's own connect/disconnect action, which
is unaffected by this change.

ShowConnectionState()'s skinChanged parameter was only ever read by the
now-removed block, so it's gone too, along with the one call site that
passed true and the forceUpdate plumbing that reached it through
GuiEvents.cpp's ShowConnState() free function.

Left ID_BUTTONCONNECT and muuli_wdr.cpp's muleToolbar() (which still
calls it) alone -- that function predates Apply_Toolbar_Skin, is never
called anywhere, and touching pre-existing dead code is out of scope
for this fix.

Verified: built and ran both amule and amulegui cleanly, full unit
test suite passing (26/26). Confirmed via the po/ diff that "Connect"/
"Disconnect"/"Cancel" remain in the catalog (still used by other
per-network buttons) -- only their now-orphaned toolbar-specific
tooltip strings were dropped. Could not get a real on-screen visual
confirmation of the toolbar layout -- same macOS Accessibility
automation limitations as #180 got in the way of screenshotting the
actual app window.

* i18n: regenerate po/ catalogs after rebasing onto current master

Mechanical rebase to resolve conflicts against master's #642/#643 --
no source changes here. Resolved a real conflict in amuleDlg.cpp's
event table: #642 added the Alt+<letter> EVT_MENU block right where
this branch removed the EVT_TOOL(ID_BUTTONCONNECT, ...) line; kept
#642's block, dropped the connect-button line as intended.

Regenerated again via scripts/update-po.sh after later rebasing onto
master's #671 (Preferences sidebar) and #665/#662 (amuleapi
credentials, sparse part-file setting), which had added strings this
branch's catalogs didn't have yet.

* feat(gui): turn the per-network Disconnect buttons into Connect/Cancel/Disconnect toggles

Relocates the removed global toolbar button's functionality into the
ED2K (IDC_ED2KDISCONNECT) and Kad (ID_KADDISCONNECT) panes instead of
just dropping it, per got3nks's proposal on #663: each button now
mirrors ed2kState/kadState (off/connecting/connected) using the
existing connButImg() bitmaps, and can independently connect or
cancel/disconnect its own network -- something the old OR-toggled
global button never allowed. The ED2K pane also gains a Connect
button it never had (previously Disconnect-only).

CServerWnd::UpdateED2KConnectButton() / CKadDlg::UpdateConnectButton()
are called from CamuleDlg::ShowConnectionState() alongside the
existing UpdateED2KInfo()/UpdateKadInfo() calls, and once from each
pane's own ctor/Init() for the initial paint. OnBnClickedED2KDisconnect
and OnBnClickedDisconnectKad gained the missing "connect when off"
branch; OnBnConnect and the tray icon are untouched.

Verified interactively on Windows: both buttons show the correct
label/icon for their live state, ED2K disconnect/reconnect works from
its own button, and disabling ED2K in Preferences while Kad stays
connected confirms the two toggle independently.

po/ regenerated via scripts/update-po.sh to match the new button
labels ("Disconnect Kad" is now unused and moves to the obsolete
section; no new translatable strings).

* fix(gui): drop the per-network connect-button state cache

got3nks flagged two bugs in review (PR #663): the static cache's
early return also skipped Enable(), so the button could get stuck
disabled once IsReady()/network-pref state changed without the
Connect/Cancel/Disconnect state itself changing (master had two now-
deleted escape hatches -- the skinChanged force-path and an
unconditional EnableTool call -- that used to paper over this). Worse,
the cache was function-scope static, surviving widget recreation, so
a fresh pane's Init() call could early-return and leave the wxDesigner
default label ("Disconnect Kad") on screen.

Dropping the cache removes both: SetLabel/SetBitmap/Enable are cheap
on a plain wxButton (unlike the old toolbar tool, which had real
native-image-list reasons to memoize, see amuleDlg.cpp:1054-ish), and
master's own second call site already did an unconditional EnableTool
every tick without issue.

Second review round (macOS + Ubuntu ARM64, monolithic + amuleGUI)
confirmed the toggle works functionally but flagged three cosmetic
issues, all fixed here:

- No gap between the connect-button icon and its label. The obvious
  fix, wxButton::SetBitmapMargins(), is NOT portable (wxOSX overrides
  it, wxGTK inherits the base no-op) -- prefixing the label with a
  literal space outside the _() call behaves identically on both and
  introduces no new msgid.
- The Kad button was stretched full-width by its sizer's .Expand()
  flag, while the ED2K one sizes naturally; GTK and macOS then filled
  the slack differently (centered vs. icon-left). Dropped .Expand()
  so the two buttons size the same way -- trades away lining up with
  the "Bootstrap from known clients" button above it, but the two
  connect buttons reading as the same control matters more.
- CServerWnd::UpdateED2KConnectButton() and CKadDlg::UpdateConnectButton()
  were near-identical (same 3-state enum, same switch, same three
  bitmaps). Factored into a shared SetConnectButtonState(button, state,
  enabled) helper in muuli_wdr.cpp/.h, next to connButImg() which it
  reuses -- so the icon-spacing fix above lives in one place instead
  of two.

Verified interactively on Windows: re-tested full disconnect/reconnect
cycle on both panes, toggling ED2K off and back on via Preferences
(existing behavior: needs an app restart to re-enable) with correct
state on the fresh post-restart paint, and confirmed by screenshot
that both buttons now show a visible icon/label gap and size the same
way (Kad no longer stretched). Also confirmed amulegui (CLIENT_GUI)
builds clean with these changes; did not perform full interactive
remote-daemon testing.

* fix(gui): first-paint layout bug + connect-button placement, per review

Two more issues from got3nks's Linux/GTK pass on #663.

1. Bug: the ED2K connect button painted with the icon overlapping the
   label on first show (Kad was fine, only by luck of timing).
   CServerWnd::UpdateED2KConnectButton() runs after sizer->Show(this,
   TRUE) in the ctor, so SetBitmap() grows the button's best size
   after the row was already measured against the text-only label,
   and nothing re-lays it out until a window resize. Fixed by calling
   Layout() on the button's parent at the end of the shared
   SetConnectButtonState() helper -- covers both panes, and stops Kad
   from relying on being laid out late (its notebook page is only
   measured once shown, after Init() already set its bitmap).

2. Layout: moved both connect/disconnect toggles to lead their pane
   instead of sitting among secondary controls, per got3nks's request:
   - ED2K (serverListDlgUp): toggle now on its own top row, right-
     aligned via a stretch spacer, directly above the server list.
     The "Add server manually" form (name/IP/port/Add) moved below
     the table instead of sharing a row with the toggle; the vertical
     separator that used to sit between them is gone (no longer
     needed once they're not adjacent).
   - Kad (KadDlg): toggle moved out of the "Bootstrap" static box
     (where it sat under "Bootstrap from known clients") to its own
     top row above the box, right-aligned the same way as ED2K's.
     item0 changed from a 1x1 FlexGridSizer (which only ever held the
     two-column area) to a plain vertical wxBoxSizer so it can hold
     the new top row plus the existing two-column layout.

Both panes now read "primary control on top, secondary/manual
controls below" -- matching shape on both tabs.

Verified interactively on Windows: screenshotted both panes, confirmed
no icon/label overlap on first paint (no window resize needed), and
re-ran the disconnect/reconnect toggle cycle on both after the layout
change.
got3nks added a commit that referenced this pull request Jul 29, 2026
…able, Web UI label CI gate (#655) (#696)

* refactor(amuleapi): make /preferences field names self-explanatory (#655)

Rename 45 of the 119 fields GET/PATCH /api/v0/preferences exposes so each
one states what it does without a lookup table, and nest the two unrelated
remote-control subsystems instead of prefixing every field. amuleapi has
not shipped and the protocol stays at v0, so the old names are simply
removed rather than dual-emitted.

The EC protocol is untouched: every tag number, presence-vs-value encoding
and thePrefs:: accessor stays as-is, so amulegui / amulecmd / amuleweb
remain compatible. All translation happens at the webapi boundary, the
same way extended_udp_port_enabled already inverts EC_TAG_CONN_UDP_DISABLE.

Highlights, by the class of problem each fixes:

- Misleading names: security.obfuscation_supported -> obfuscation_enabled
  (it is a writable preference, while _supported / _available elsewhere
  means a read-only build capability); message_filter.friends / .secure /
  .all -> accept_from_friends_only / accept_from_known_clients_only /
  filter_all_messages; directories.exclude_regex ->
  exclude_patterns_use_regex (a modifier on exclude_patterns, not a second
  list); files.preview_prio -> prioritize_first_last_chunks.
- Missing units: core_tweaks.srv_keepalive_timeout ->
  server_keepalive_timeout_ms (its two siblings already said _ms),
  connection.slot_allocation -> upload_slot_kbps, filebuffer ->
  file_buffer_bytes, online_signature.update_frequency ->
  update_frequency_seconds, webserver refresh -> refresh_seconds.
- Magic numbers: connection.proxy_type and security.can_see_shares (now
  shared_files_visibility) become enum strings. A new PrefTakeEnum helper
  maps them to the wire ints and also replaces the hand-rolled inline
  mapping ip2country.source was using.
- Structure: remote_controls.{webserver,amuleapi}.{...} replaces the nine
  webserver_* / amuleapi_* prefixes. Both sub-objects still pack into the
  single EC_TAG_PREFS_REMOTECTRL group, and passwords stay write-only.
- Abbreviations spelled out: dl / ul / prio / cat / srv / autoconn.

files.create_normal is deliberately left alone. It is the one field whose
positive name requires inverting its meaning, so it gets its own change.

The Web UI moves with the API in the same commit: field keys, the derived
prefs_field_* dictionary keys in both locales, and dotted category paths
for the nested objects. Two labels copied verbatim from the desktop are
reworded ("Slot Allocation" -> "Upload speed per slot (KB/s)"), and the
keepalive field now scales to minutes like the desktop slider instead of
showing a bare millisecond count.

check-i18n.mjs gains a check that every preferences field resolves to a
real label, and is wired into the i18n workflow. These label keys are
derived from the field names, so before this a renamed field with no
dictionary entry would have rendered the raw key with nothing failing.

Verified: full macOS build clean, 28/28 ctest green, curl phases 05 (73)
and 15 (117 incl. new nested remote_controls and enum coverage) pass
against a live daemon. Documented and emitted key sets diffed to zero.

* refactor(amuleapi): drive /preferences from one declarative field table (#655)

The 119 fields on /api/v0/preferences were described three times, in three
different idioms: a hand-written EC decode per category in Refresher, a
w.Key/w.Value pair per field in the GET emitter, and a PATCH applier that
had grown two parallel helper families -- 87 call sites on the generic
PrefTake* helpers plus 22 more on connection-local lambdas doing the same
job with their own error strings and a different return convention. A field
touched in two of the three and missed in the third compiled cleanly.

Replace all three with a walk over one table (PrefsSchema.{h,cpp}). Each row
names a field's JSON category and key, its EC tag, its type, how EC encodes
it, and whether it is readable, writable or neither. Adding a preference is
one row; renaming one is one token.

What used to be special-case code is now a column:

- `invert` -- extended_udp_port_enabled, whose EC tag (EC_TAG_CONN_UDP_DISABLE)
  is negatively named. This is also what makes the deferred create_normal ->
  create_sparse_files flip a one-value change instead of two structurally
  different edits on the read and write paths.
- `enc` -- presence-tag vs value-tag booleans, which the core serializer mixes
  (57 presence, 9 value) and which every reader previously had to know.
- `access` -- read-only capabilities and live status, write-only passwords and
  the ip2country trigger, and the amuleapi passwords that belong to
  /auth/passwords and are rejected here.
- `gated_by` -- the 409 when files.mmap_enabled is set against a core built
  without mmap.
- `read_group` -- connection.upnp_available, the one field whose EC tag lives
  in a different group ([General]) than its JSON category implies.

Rows carry the address of their backing member, and the PREF_* macros
static_assert that the declared PrefType matches the member's real C++ type,
so a mis-typed row fails the build rather than misbehaving at runtime.

One field genuinely cannot be described: remote_controls.webserver
.guest_enabled and .guest_password share a single EC tag, which carries the
enable bool as its value and the password hash as a child. There is no 1:1
field-to-tag mapping to write down, so its PATCH packing stays hand-written
and the schema marks it Bespoke. Its GET emission is still table-driven.

Equivalence was checked against a captured GET /preferences from the previous
build: the EC decode and the GET emitter each produce byte-identical output,
and the final key set matches at 119/119. PATCH is covered by curl phase 15
(117/117, unchanged) plus direct checks that every type, the inverted field,
the nested categories and each error path still behave. Error bodies are now
uniform ("<key> must be a bool") where they used to be per-category
("connection field must be a bool"); no test or doc asserted the old wording.

Two new tests pin the table's own invariants: no duplicate fields, every
category resolving to an EC group, members present exactly for the rows that
round-trip a value, enum rows carrying a name table, capability gates naming a
real sibling bool, and the emitted count staying at the documented 119. A
second test asserts the three irregularities above stay at one field each, so
a second one appearing is caught rather than copied.

Net: -1437 / +842 lines, with the prefs handling in Api.cpp down 912 lines and
Refresher.cpp down 212.

* feat(amuleapi)!: state the sparse-file preference positively (#655)

files.create_normal becomes files.create_sparse_files, and its meaning
inverts: create_sparse_files == !create_normal. This is the one field in the
payload whose positive name could not be reached by renaming alone, so the
issue asked for it on its own commit.

Every layer except EC already spoke in terms of "sparse". The config key is
/eMule/CreateSparseFiles, the core stores s_createFilesSparse (default on),
PartFile.cpp reads CreateFilesSparse(), and the desktop checkbox added in
#662 is labelled "Create new files as sparse files". Only the EC tag is named
for the negative case -- EC_TAG_FILES_CREATE_NORMAL is an empty tag present
only when sparse is off -- and amuleapi was the one consumer that took that
name and published it outward, leaving the Web UI saying "non-sparse" where
the desktop says "sparse" for the same setting.

EC is untouched: same tag number, same presence semantics, same
thePrefs::CreateFilesNormal(). The negation is undone at the webapi boundary
instead, exactly as extended_udp_port_enabled already does for
EC_TAG_CONN_UDP_DISABLE. Thanks to the schema table this is one column:

    PREF_BOOL("files", "create_sparse_files", EC_TAG_FILES_CREATE_NORMAL,
              PrefEnc::Presence, /*invert=*/true, ...)

The snapshot default flips to true to match the core's own default, and the
Web UI label moves to the desktop's positive wording, which also flips the
checkbox's rendered state for an unchanged setting.

Verified end to end against a daemon's own config file rather than just the
API's echo, since a polarity change is precisely where a self-consistent API
can still be wrong:

  CreateSparseFiles=1  -> GET create_sparse_files: true
  PATCH false          -> CreateSparseFiles=0 in amule.conf
  CreateSparseFiles=0  -> GET create_sparse_files: false
  PATCH true           -> CreateSparseFiles=1 in amule.conf

The schema-invariant test caught this change on its own: it enumerates which
fields may invert, so adding a second one failed until the list was updated
deliberately. That is the check working, and the list now names both tags and
why each is negative.

Note for clients: this is the one field where renaming the key without
flipping the value yields the opposite behaviour. Nothing ships with the old
name -- amuleapi is unreleased and the protocol stays at v0 -- so no
compatibility shim is carried.

* fix(amuleapi): keep the enum index unsigned in the prefs decoder

CECTag::GetInt() returns uint64_t, so copying it into an std::int64_t
narrows, which bugprone-narrowing-conversions flags on a new line. Keep the
index unsigned: the >= 0 half of the range check was dead weight anyway, and
the remaining bound is the only one that matters.
@LSalami
LSalami deleted the add-sparse-files-prefs-ui branch August 5, 2026 14:12
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.

Preferences: add a GUI control for the sparse part-file setting (/eMule/CreateSparseFiles)

2 participants