feat(gui): amuleGUI remote-to-local path mappings (#843) - #854
Conversation
|
Verified the design claims hold: path mappings never reach Three things to fix, the first two in the same few lines of 1. A trailing separator on the remote prefix silently corrupts every path it maps. The substitution is Fix: strip trailing separators from both prefixes where they are accepted ( 2. The prefix match has no boundary check. Fix: require the character after the prefix to be a separator, or the whole string to match. 3. The explanatory paragraph does not use the panel width. Fix: drop the Minor: The known gap you flagged is the right call — the substitution is separator-agnostic string logic, and (1) above is the part that actually needs a Windows-shaped path to shake out. |
|
#859 has landed on master, and it adds two helpers your path-mappings editor should use for its local-prefix column. Rebasing onto master will bring them in. Background: writing They sit in an anonymous namespace above Header, next to std::vector<CPath> m_pathMappingRowPaths;
list->DeleteAllItems();
m_pathMappingRowPaths.clear();
...
list->InsertItem(row, mapping.remotePrefix);
SetListRowPath(list, row, 1, mapping.localPrefix, m_pathMappingRowPaths);
mapping.remotePrefix = list->GetItemText(row); // unchanged, see below
mapping.localPrefix = GetListRowPath(list, row, m_pathMappingRowPaths);
const long row = list->InsertItem(list->GetItemCount(), remote);
SetListRowPath(list, row, 1, CPath(local), m_pathMappingRowPaths);
The three points from the earlier review still stand independently of this. |
FileLaunch::Open()/Reveal() silently disable themselves whenever the daemon-reported path doesn't exist verbatim on amuleGUI's own machine, which is always true for a genuinely remote daemon unless the exact same absolute path happens to be independently mounted. Lets the user configure a table of remote->local path-prefix mappings (e.g. the daemon's /downloads/incoming reachable here as D:\Downloads\aMule\incoming via a Samba mount) so those actions work against a remote daemon whose filesystem is otherwise reachable. Storage is genuinely GUI-local: CPreferences::PathMapping / Load|SavePathMappings() read and write wxConfigBase::Get() directly (which already resolves to remote.conf under CLIENT_GUI), never through LoadAllItems()/SaveAllItems()'s Cfg_Base walk and never added to CPreferencesRem's m_exchange_send_selected_prefs / m_exchange_recv_selected_prefs -- so this never round-trips over EC. Two existing "list of items" precedents looked reusable but weren't: Categories and the shared-dirs editor both look locally persisted but are actually daemon-owned, silently overwritten by the next EC pull. Took SaveCats()'s per-row wxConfigBase group shape without its EC-backed data source. Applied in FileLaunch::ResolvePath(), the single choke point every Open/Reveal/availability check already goes through (six call sites across DownloadListCtrl.cpp and SharedFilesCtrl.cpp, none touched). ApplyPathMapping() does plain string-prefix substitution on the raw daemon path before it becomes a CPath -- CPath has no notion of a second machine's separator convention, so the remote side is compared and rewritten as a string, never parsed as this host's path syntax. First matching prefix in the user's list order wins (no implicit longest-prefix-match). New "Path Mappings" Preferences page, CLIENT_GUI-only, modelled on the existing shared-dirs list editor's widget shape (wxListCtrl + text entry + Add/Remove) but without its EC round-trip/dirty-flag/session machinery, which exists only to survive a background daemon refresh that cannot happen to a purely local list. Known gap: developed and tested on macOS. The substitution itself is separator-agnostic pure string logic, and mingw-w64 CI confirms the Windows build compiles, but an actual Samba-mounted drive-letter/UNC mapping has not been exercised end-to-end this round. Verified: amule (CLIENT_GUI off) and amulegui (CLIENT_GUI on) build clean from a fresh CMake configure; pinned clang-format v18 clean; Tier-1 clang-tidy clean via the local CI replica against upstream/master (caught and fixed one real hit: an unconditional CPath copy in ResolvePath() that's wasted work on the non-CLIENT_GUI build, where the mapping branch never runs).
…ase onto master got3nks's review on amule-org#854 (github.com/amule-org/pull/854): 1. A trailing separator on the remote prefix silently corrupted every path it mapped: the substitution is localPrefix + remainder, and CPath's constructor does not strip trailing separators, so remote "/downloads/" + local "/Volumes/dl" produced "/Volumes/dlincoming/f.avi" instead of "/Volumes/dl/incoming/f.avi". Browse never supplies a trailing separator (wxDirDialog::GetPath() doesn't return one) but a user describing a remote *directory* naturally types one, so this was the likely path through the UI, not a corner case. Fixed by stripping trailing separators from both prefixes wherever they're accepted: OnPathMappingAdd() (entry) and LoadPathMappings() (an existing config saved before this fix, or hand-edited). 2. ApplyPathMapping()'s prefix match had no boundary check: a mapping for "/mnt/data" also matched "/mnt/data-old/f.avi". Fixed by requiring the character after the prefix to be a separator (either convention, since the daemon's OS isn't known here) or the whole string to match. 3. The explanatory paragraph above the mapping list didn't use the panel width: muuli_wdr.cpp's one-shot Wrap(380) baked fixed line breaks at construction while the sizer's Expand() only stretched the control, leaving an empty right margin, and never re-flowed on resize (the dialog has wxRESIZE_BORDER) or DPI. Fixed by dropping the Wrap() from muuli_wdr.cpp (layout construction only, per that file's convention) and re-wrapping from the control's own real, DPI-scaled client width in PrefsUnifiedDlg -- once in PopulatePathMappingList() and again on every resize via a runtime Bind(wxEVT_SIZE) (size events don't propagate through the static event table the way command events do, so this can't be a wxDECLARE_EVENT_TABLE() row). Minor: OnPathMappingAdd() also now tells the user why nothing happened on empty/duplicate input instead of silently no-opping. Also rebased onto current master to pick up amule-org#859, which fixed the same "cell text is not a round-trippable CPath" bug (macOS NFD-normalisation mismatch) in the shared-dirs editor this one was modelled on, and extracted SetListRowPath()/GetListRowPath() for exactly this reuse. Path-mapping's local-prefix column now goes through them instead of GetPrintable()/CPath(text) round-tripping by hand. Verified: rebuilt amule, amuled and amulegui locally, all clean. po/ regenerated -- msgid delta +13/-0 (11 carried over from the original amule-org#843 strings the rebase's po/ conflict resolution reset to upstream, plus 2 new validation-message strings from this fix).
32c56e8 to
41e29a3
Compare
|
All three, plus the minor, addressed:
Also rebased onto current master and switched the local-prefix column over to #859's Rebuilt amule/amuled/amulegui locally, all clean. po/ regenerated (msgid delta +13/-0: the 11 original #843 strings the rebase's po/ conflict resolution reset to upstream, plus 2 new validation-message strings). Local clang-tidy CI replica against |
The re-wrap added for the review's layout point read the paragraph's own width and rewrote the paragraph from inside that paragraph's size handler. wxStaticText::SetLabel() resizes the control to fit its new label, so the handler fed itself: restoring the unwrapped text to re-flow made the control briefly as wide as the whole sentence, that width came back as another size event, and the two alternated until the stack was gone (EXC_BAD_ACCESS, "excessive recursion", inside SetLabel under NSView setFrameSize). Bound on the page instead. A page's width is set by the dialog and is unmoved by anything its children do, so the input to the wrap no longer depends on what the wrap changes and the loop is gone structurally rather than by hoping the widths converge. Re-wrapping is additionally skipped when the width has not moved, so a height-only relayout costs nothing, and page->Layout() runs afterwards so the list follows the paragraph's new height. Two things the earlier version could not do, now fixed with it: Wrap() only ever inserts breaks -- it re-reads the current label and treats newlines already in it as hard -- so wrapping in place could narrow the text but never rejoin it. Widening the dialog left the paragraph at its old narrow width, which is the empty right margin the review was about, just reached by resizing instead of baked in. The unwrapped text is kept and restored before each wrap. muuli_wdr.cpp wraps once again at construction, which the fix had dropped. An unwrapped wxStaticText reports its whole single line as its best width and the sizer turns that into the page's minimum, so the dialog opened as wide as the sentence. It is bounded there and re-flowed from here; the bound is parent->FromDIP(380) rather than a raw pixel count, which was the part that read cramped on a HiDPI display. Also adds the include for StripSeparators, which Preferences.cpp had been getting transitively. Tested on macOS: the page opens at a sane width, the paragraph fills it, and it re-flows both wider and narrower under a continuous drag.
|
Checked all three fixes against the code and they're right — the strip at load as well as at entry is the part I'd have missed, since it repairs a config written before the fix. I've pushed one commit on top rather than send you round again for it ( It reworks the hint re-flow. Bound on the paragraph, the handler fed itself: Two more from the same area. Also added the Tested on macOS: opens at a sane width, fills it, re-flows both ways under a continuous drag. One thing left alone: stripping means a prefix of just |
…ions Two gaps the trailing-separator trim left, both about the mapping's two halves coming from machines that need not agree on a separator. The trim used StripSeparators(), which consults *this* host's separator set. On a POSIX build that set has no backslash, so a Windows daemon's "D:\dl\" kept its trailing separator and the join ran the halves together -- exactly the corruption the trim exists to prevent, alive in the mirror direction (a Linux or macOS amulegui against a Windows daemon). The remote prefix is now trimmed by CPreferences::TrimRemotePrefix(), which accepts either convention because the daemon's OS is not knowable here -- the same reason the prefix boundary test already accepts either character. And the remainder spliced onto the local prefix keeps the daemon's separators, so a POSIX daemon feeds '/' into a path about to be handed to Win32. Most of Win32 takes that, but "explorer /select," -- which is what Reveal() runs, and the reason this feature exists -- is the fussy one. Normalised on Windows only: '/' cannot occur in a Windows filename, whereas a backslash is an ordinary character in a POSIX one, so the mirror rewrite would corrupt names instead of fixing separators. Verified on Windows that Win32 resolves mixed separators (Test-Path on "C:\dir/sub/f.txt" is true), so availability and Open were already fine there; this is for Reveal and for the POSIX-client-Windows-daemon direction.
|
One more commit ( The trailing-separator trim used And the remainder spliced onto the local prefix keeps the daemon's separators, so a POSIX daemon feeds Checked on a Windows VM with a real SMB share mapped to a drive letter: |
Catalogs taken from master, which amule-org#854 regenerated after the previous merge; this branch's strings are re-added by the regeneration below.
…red Files (amule-org#843 follow-up) Feedback from ghysler on amule-org#843 after PR amule-org#854 merged (github.com/amule-org/issues/843): 1. The Path Mappings preferences page shared "prefs_directories" with the Directories page -- same icon for two different pages in the sidebar. Added a dedicated prefs_pathmapping.svg/.png: the same folder construction as prefs_directories.svg, recoloured to a network-blue palette so the two read as distinct at a glance, with a small sync-arrows badge for the "mapped elsewhere" idea. Regenerated icon_data.c via embed_icons.py. 2. Double-clicking a completed file in the Shared Files panel opened the file-details modal instead of launching it; right-click -> "Open the file" already worked, and the Downloads panel's double-click already launches. CSharedFilesCtrl::OnItemActivated unconditionally called ShowFileDetailDialog() -- it never had the CDownloadListCtrl-style open/preview gate to begin with. Now mirrors both CDownloadListCtrl::OnItemActivated (read the row off the event rather than touching the selection, so a double-click doesn't discard a multi-selection) and the same gate already used by this file's own MP_VIEW context-menu entry: a finished file of any type opens, an unfinished one (CSharedFileList also carries PS_READY part files) only once enough of the media is on disk to play. Anything else still opens the details modal, unchanged from before. Verified: rebuilt amule, amuled and amulegui locally, all clean. Rendered the new SVG standalone at 16x16 and 128x128 to confirm it's legible and visually distinct from prefs_directories at both sizes. Wasn't able to verify the icon live in a running Preferences dialog this round -- the local amuleGUI/amuled test pairing hit an EC auth issue unrelated to this change (a hand-edited test daemon password hash, not a real deployment concern) that wasn't worth chasing further for a one-line icon-name swap; happy to redo that check if wanted before merge.
…red Files and completed Downloads (#868) Follow-up to #843, from ghysler's feedback after #854 merged. The Path Mappings preferences page shared prefs_directories' icon with the Directories page -- the same icon for two different sidebar entries. It now has its own prefs_pathmapping.svg/.png: the same folder construction, in a network-blue palette so the two read as distinct at a glance, badged with a double-headed arrow for the "mapped elsewhere" idea. The badge went through two rounds: a pair of curved sync arrows is the conventional drawing, but at 16x16 the disc is barely 7px across and two separate arcs inside it render as a featureless blob at any stroke weight, so the glyph is a single shaft with a head at each end, white on amber. icon_data.c regenerated via embed_icons.py. Double-clicking a completed file in Shared Files opened the file-details modal instead of launching it, where right-click -> "Open the file" already worked and the Downloads panel already launched. CSharedFilesCtrl::OnItemActivated called ShowFileDetailDialog() unconditionally -- it never had a launch gate at all -- and now reads the row off the event rather than touching the selection, so a double-click no longer discards a multi-selection. Both panels ended up on one rule: !IsPartFile() || PreviewAvailable(). A completed file opens whatever type it is; an unfinished one opens only once enough of the media is on disk to play, which is PreviewAvailable()'s own test. Downloads moved to match rather than Shared Files being narrowed, because the media restriction there came entirely from PreviewAvailable() and made a double-click mean two different things on the same file: a download that has just completed is listed in both panels at once.
Closes #843.
Problem
FileLaunch::Open()/Reveal()— "open this download", "show in file manager" — silently disable themselves (FileLaunch::GetAvailability()) whenever the daemon-reported path doesn't exist verbatim on amuleGUI's own machine. That's always true for a genuinely remote daemon, unless the exact same absolute path happens to be independently mounted. This adds a user-configurable table of(remote prefix → local prefix)mappings — e.g. the daemon's/downloads/incomingreachable locally asD:\Downloads\aMule\incomingvia a Samba mount — so those actions work against a remote daemon whose filesystem is otherwise reachable some other way.Design
Storage is genuinely GUI-local.
CPreferences::PathMapping/Load|SavePathMappings()read and writewxConfigBase::Get()directly (already resolves toremote.confunderCLIENT_GUI), never throughLoadAllItems()/SaveAllItems()'sCfg_Basewalk, and never added toCPreferencesRem'sm_exchange_send_selected_prefs/m_exchange_recv_selected_prefs— so this never round-trips over EC. Two existing "list of items" precedents looked reusable but weren't: Categories and the shared-dirs editor both look locally persisted but are actually daemon-owned, silently overwritten by the next EC pull. This takesSaveCats()'s per-rowwxConfigBasegroup shape without its EC-backed data source.Applied in
FileLaunch::ResolvePath(), the single choke point every Open/Reveal/availability check already goes through (six call sites acrossDownloadListCtrl.cppandSharedFilesCtrl.cpp, none touched).ApplyPathMapping()does plain string-prefix substitution on the raw daemon path before it becomes aCPath—CPathhas no notion of a second machine's separator convention, so the remote side is compared and rewritten as a string, never parsed as this host's path syntax. First matching prefix in the user's list order wins (no implicit longest-prefix-match).New "Path Mappings" Preferences page,
CLIENT_GUI-only, modelled on the existing shared-dirs list editor's widget shape (wxListCtrl+ text entry + Add/Remove) but without its EC round-trip/dirty-flag/session machinery, which exists only to survive a background daemon refresh that cannot happen to a purely local list.Known gap
Developed and tested on macOS. The substitution itself is separator-agnostic pure string logic, and mingw-w64 CI confirms the Windows build compiles, but an actual Samba-mounted drive-letter/UNC mapping has not been exercised end-to-end. Flagging this explicitly rather than claiming full cross-platform verification.
Verification
amule(CLIENT_GUIoff) andamulegui(CLIENT_GUIon) build clean from a fresh CMake configure — confirms the new page/storage compile out entirely on the monolithic sideupstream/master) clang-tidy clean via the local CI replica — Tier-1 caught and I fixed one real hit: an unconditionalCPathcopy inResolvePath()that was wasted work on the non-CLIENT_GUIbuild, where the mapping branch never runs