feat(metadata): advertise Length / Bitrate / Codec for shared media files via ffprobe - #280
Merged
Merged
Conversation
got3nks
added a commit
that referenced
this pull request
Jul 3, 2026
Clicking any Browse button inside Preferences on macOS (video player picker on IDC_BROWSEV, browser picker on IDC_SELBROWSER, and — once #280 lands — the ffprobe path picker on IDC_MEDIAMETA_FFPROBEBROWSE) visually closes the whole Preferences dialog on both Open and Cancel. Debug-tracing the dialog state showed it was still logically alive after wxFileSelector returned (IsShown() == true, IsBeingDeleted() == false, no wxEVT_CLOSE_WINDOW fired), so the disappearance wasn't a close event we were mishandling. The modal NSOpenPanel that wxCocoa opens steals key-window status while shown; when it dismisses, Cocoa returns focus + Z-order to whichever aMule window was active before Preferences opened, not to Preferences itself. The dialog sits alive but ordered behind the main aMule window — Cmd+Tab back to aMule brings it right up. Fix: call Raise() on Preferences after wxFileSelector returns on macOS. Wrapped in #ifdef __WXMAC__ so wxGTK / wxMSW pay nothing (they don't reproduce the bug — the Windows CommonDialog is a real top-level and wxGTK's file dialog doesn't reshuffle Z-order the same way).
Standalone module that fronts an ffprobe subprocess to extract length / bitrate / codec from local shared files. Wiring into share-add and Preferences follows in subsequent commits. MediaProbe::AutoDetectPath() locates a usable binary in two steps: first a bare `ffprobe -version` invocation to catch the PATH-installed case, then a per-platform well-known-paths scan (Homebrew + MacPorts on macOS; Chocolatey + Scoop + WinGet layouts on Windows; distro- standard prefixes plus /snap/bin on Linux + OpenBSD). Fallback matters because GUI-launched processes get a minimal PATH on macOS (launchd default lacks /opt/homebrew) and unreliable PATH on Windows (system- level updates from Chocolatey install don't always propagate to running processes). MediaProbe::Probe() forks ffprobe with `-of default=nk=0:nw=1` so the output is a bare `key=value` stream — no JSON parser dependency needed (the tree has none). Length rounds to whole seconds (FT_MEDIA_LENGTH's wire format is uint32), bit_rate is converted from bps to kbps (FT_MEDIA_BITRATE), and the first stream's codec_name wins (video for video containers, audio for audio-only). Failures emit debug log lines but never surface user-visible errors — a file the probe can't read still shares fine, just without media tags. Boolean return with out-param instead of std::optional so the module matches the existing tree's C++ style. Callers MUST run Probe() off the main thread; a subsequent commit extends SharedFileList's batch path to do so. Prep for amule-project#140 Phase B — advertising media metadata from own shared files to ed2k servers + Kad.
The Kad publisher at kademlia/kademlia/Search.cpp:1422 already iterates the file's media tags — but gates the whole emit on `file->GetMetaDataVer() > 0`. GetMetaDataVer had been a longstanding stub returning a hardcoded 0 with a TODO comment, so the code path was dead: no ed2k client has ever seen media metadata from an aMule-shared file. The ed2k server publish path (CreateOfferedFilePacket) had a matching stub — a comment reading "There, we could add MetaData info, if we ever get to have that." that built its outgoing tag list without touching m_taglist for media entries. Un-stub both: - GetMetaDataVer now derives from FT_MEDIA_LENGTH tag presence. No new persisted field: MediaProbe is the only source of the tag, and the tag itself already rides through known.met via the existing m_taglist load/save. Non-zero length is the "we've probed and have data worth publishing" signal, which is exactly what Kad's gate needs. - CreateOfferedFilePacket appends FT_MEDIA_LENGTH / FT_MEDIA_BITRATE as CTagVarInt (VBT-encoded for capable eMule clients + TYPETAG- INTEGER-capable servers, fixed 32-bit otherwise) and FT_MEDIA_CODEC as CTagString when the corresponding tag is present and non-empty / non-zero. Each tag is optional per file; a file with only length publishes only length. Prep for amule-project#140 Phase B — once MediaProbe wiring lands in SharedFileList's share-add path, tags flow to peers automatically. No change to known.met format (m_taglist already covers the persistence layer).
Preferences plumbing for amule-project#140's ffprobe subprocess. Cfg items live in a fresh /MediaMetadata/ config namespace (won't clash with any /eMule/ key) with Enabled default false — an upgraded install won't kick off background probing until the user opts in from Preferences -> Files. UI panel goes at the bottom of PreferencesFilesTab: an Enable checkbox plus a "Path to ffprobe:" row with text field, Browse and Detect buttons. Browse routes through the existing OnButtonBrowseApplication switch (same file-selector infra the video-player / browser fields use). Detect fires MediaProbe::AutoDetectPath() and either populates the field or shows a friendly "install ffmpeg or Browse manually" info dialog. The whole box hides in amulegui via PrefsUnifiedDlg's amuledOnlyPrefs[] — probing runs daemon-side, the remote GUI has no business setting the path. IDC IDs sit in the 10420-10423 functional band and 10370 orphan- label slot, chosen to leave a 10-ID cushion above bind-to- interface's IDC_INTERFACE = 10410 so parallel branches can grow without collision. MediaProbe moves to COMMON_SOURCES so the Detect button's handler still links in remote-GUI builds (the button is hidden there but the event-table binding still needs the symbol). Follow-ups on the same branch: SharedFileList wiring (probe on share-add + known.met load), extension gate, background-probe throttling.
Closes the amule-project#140 wiring loop: shared audio / video files now get probed with ffprobe, and the resulting FT_MEDIA_LENGTH / _BITRATE / _CODEC tags are attached to the CKnownFile so the earlier ed2k + Kad publisher un-stubs pick them up. Threading + throttling: - New CMediaProbeTask (ETP_Low) rides on the existing CThreadScheduler queue. That queue serialises tasks — a large library at first-boot-after-upgrade retrofits one file at a time, never stepping on hashing or completion which run at higher priority. No custom thread pool. - Task ctor snapshots the ffprobe path so the worker never touches thePrefs, and remembers just the file's CMD4Hash + CPath. Result marshals back via CMediaProbeEvent (new MULE_EVT_MEDIA_PROBE), main thread resolves the hash to a live CKnownFile via CKnownFileList::FindKnownFileByID (the file may have been unshared while we were probing) and calls AddTagUnique for each populated field, MarkECChanged, and knownfiles->Save so the tags survive a crash. Gating: - Preference /MediaMetadata/Enabled must be true AND FFProbePath non-empty. - Extension gate: only ED2KFT_AUDIO or ED2KFT_VIDEO files (from GetED2KFileTypeID) — skips the mass of docs / archives / images a typical share tree carries. - Already-probed gate: skip when FT_MEDIA_LENGTH > 0. This is the retrofit cache — once a file has been probed successfully its tag rides through known.met and future launches skip re-probing. Hook site is CSharedFileList::AddFile — the single choke point for both runtime share-adds (user picks a new folder / drops a file in Incoming) AND Reload()'s known.met walk, so an upgraded install retrofits every existing shared file on the first launch after the user enables the feature. No separate load-vs-add code path. Event-table bindings for MULE_EVT_MEDIA_PROBE landed in both amuled.cpp (daemon) and amule-gui.cpp (monolithic / remote GUI) so the handler fires regardless of which app the scheduler dispatched from.
Rerun of scripts/update-po.sh on top of the post-amule-project#281 (bind-to- interface) master so the Media metadata strings coexist with the new bind-to-interface strings. Replaces the earlier standalone regen that was skipped during the rebase.
got3nks
force-pushed
the
feat/media-metadata-probe
branch
from
July 3, 2026 14:56
94f7e18 to
ddd4e21
Compare
The [MediaMetadata] Enabled checkbox now drives the enabled state of IDC_MEDIAMETA_FFPROBEPATHTEXT / _FFPROBEPATH / _FFPROBEBROWSE / _FFPROBEDETECT via the existing OnCheckBoxChange handler, and the initial state is applied at dialog open. Previously the path input, Browse and Detect buttons stayed live-looking with the feature disabled, which read as "I can configure ffprobe here" while nothing downstream would ever run.
Author
|
Testing summary before merge. Platforms built + smoke-tested end-to-end (drop probed mp3 → parse
Wire-side verification (temp debug branch, not in this PR):
UX polish since the last review pass:
Rebased on top of master (post #281 bind-to-interface), no IDC ID collisions (audited: 10370 label + 10420–10423 functional stay clear of bind-to-interface's 10362 + 10410 and everything else). Ready to merge on CI green. |
got3nks
added a commit
that referenced
this pull request
Jul 6, 2026
…CTag + codec fixes (#319) * Fix CTag::operator= freeing the wrong union member CTag holds an owned pointer (wxString* / CMD4Hash* / unsigned char*) in a union that is only valid for string / hash / blob / bsob tags. operator= freed that pointer keyed on the *right-hand side*'s type, so assigning a string tag onto a slot that currently holds an int freed a garbage pointer (the int's bits reinterpreted), corrupting the heap. std::vector<CTag>::erase() hits this whenever it shifts a string tag over a non-string one -- observed as a double free in a wxString destructor when a media tag set ([int length, int bitrate, string codec]) is re-attached via AddTagUnique. Free based on THIS tag's current type first (mirroring the destructor), then copy from rhs (mirroring the copy constructor). Pre-existing since well before 3.0.1; independent of the media-probe change that exposes it. * Isolate media probing on a dedicated bounded worker thread ffprobe media-metadata extraction (#280) ran on the shared CThreadScheduler alongside hashing and download completion. A slow or hung ffprobe -- readily reproducible on a headless daemon -- blocked the scheduler, wedging every download at PS_COMPLETING indefinitely and stalling shutdown. Move probing onto a dedicated CMediaProbeThread (mirrors CPartFileHashThread), so a stuck probe can only ever delay other probes, never completions or hashing. Bound and make each probe killable: the ffprobe invocation is now a native posix_spawn / CreateProcess child rather than wxExecute. wxExecute's synchronous path is uncancellable, and its async path couples termination and pipe draining to the main-thread event loop -- polling it from the worker is a use-after-free. The child is waited on with a 30s wall-clock timeout and a cancel flag that EndThread flips at shutdown; on either it is killed (by process group on POSIX, so wrapper-style ffprobe from snap/flatpak is fully reaped) and the join returns promptly. Add a dedicated "Media Probe" (logMediaProbe) debug category; probe tracing lives there rather than at normal log level. * Prefer the video track's codec (then audio) in media probing The probe reported the first stream's codec_name, which for a container whose first stream is a subtitle or data track (common in mkv: a leading subrip track) advertised e.g. "subrip" as the file's codec. Query codec_type alongside codec_name and select the first video track's codec, falling back to the first audio track's; subtitle / data streams never win.
got3nks
added a commit
that referenced
this pull request
Jul 6, 2026
…rit preview from search results (#321) * Probe media files on download completion Media-metadata probing (#280) only scheduled a probe when a file was first inserted into the shared list. A download is shared as a partfile while transferring, so on completion CPartFile::CompleteFileEnded -> SafeAddKFile re-adds it, AddFile's insert no-ops (the hash is already present), and the probe was never scheduled -- a downloaded media file only got its FT_MEDIA_* tags on the next daemon restart. Schedule the probe from the completion (alreadyCanonical) branch, now that the file is complete on disk at its Incoming path. QueueProbe only enqueues, so it never stalls completion. Force it (bypassing the already-has-FT_MEDIA gate via a new bForceReprobe argument) so the authoritative local probe overwrites any metadata inherited from the search result; startup rescans stay probe-once. Also add two guards to MaybeScheduleMediaProbe: * Never probe an in-progress download: a partfile is shared while transferring, so this fires from AddFile() during the download when there is no complete file to read (the on-disk name is <hash>.part). Skip any non-forced probe of a partfile unconditionally -- the completion re-enters with bForceReprobe set. Metadata is derived exactly once, on completion. * Skip when the resolved path is not on disk, so a stale known.met record that outlived its deleted file never hands ffprobe a path that cannot succeed. * Inherit advertised media metadata from search results as a preview A search result from a source running #280 carries the file's FT_MEDIA_LENGTH / _BITRATE / _CODEC tags, but CPartFile(CSearchFile*) copied only FT_FILETYPE / FT_FILEFORMAT and dropped the rest. Add the media tags to the inherited set so a download shows media metadata immediately while transferring, without a local ffprobe. The authoritative probe on completion re-derives and overwrites these values, so the inherited data is only a during-download preview. Source-agnostic: ed2k and Kad use the same media tag IDs (TAG_MEDIA_* == FT_MEDIA_* == 0xD3/D4/D5) and share the CSearchFile construction path, so this one change covers both.
Cflsft
pushed a commit
to Cflsft/amule
that referenced
this pull request
Jul 6, 2026
…e-org#283) Clicking any Browse button inside Preferences on macOS (video player picker on IDC_BROWSEV, browser picker on IDC_SELBROWSER, and — once amule-org#280 lands — the ffprobe path picker on IDC_MEDIAMETA_FFPROBEBROWSE) visually closes the whole Preferences dialog on both Open and Cancel. Debug-tracing the dialog state showed it was still logically alive after wxFileSelector returned (IsShown() == true, IsBeingDeleted() == false, no wxEVT_CLOSE_WINDOW fired), so the disappearance wasn't a close event we were mishandling. The modal NSOpenPanel that wxCocoa opens steals key-window status while shown; when it dismisses, Cocoa returns focus + Z-order to whichever aMule window was active before Preferences opened, not to Preferences itself. The dialog sits alive but ordered behind the main aMule window — Cmd+Tab back to aMule brings it right up. Fix: call Raise() on Preferences after wxFileSelector returns on macOS. Wrapped in #ifdef __WXMAC__ so wxGTK / wxMSW pay nothing (they don't reproduce the bug — the Windows CommonDialog is a real top-level and wxGTK's file dialog doesn't reshuffle Z-order the same way).
Cflsft
pushed a commit
to Cflsft/amule
that referenced
this pull request
Jul 6, 2026
…iles via ffprobe (amule-org#280) * feat(mediaprobe): ffprobe subprocess module for local media metadata Standalone module that fronts an ffprobe subprocess to extract length / bitrate / codec from local shared files. Wiring into share-add and Preferences follows in subsequent commits. MediaProbe::AutoDetectPath() locates a usable binary in two steps: first a bare `ffprobe -version` invocation to catch the PATH-installed case, then a per-platform well-known-paths scan (Homebrew + MacPorts on macOS; Chocolatey + Scoop + WinGet layouts on Windows; distro- standard prefixes plus /snap/bin on Linux + OpenBSD). Fallback matters because GUI-launched processes get a minimal PATH on macOS (launchd default lacks /opt/homebrew) and unreliable PATH on Windows (system- level updates from Chocolatey install don't always propagate to running processes). MediaProbe::Probe() forks ffprobe with `-of default=nk=0:nw=1` so the output is a bare `key=value` stream — no JSON parser dependency needed (the tree has none). Length rounds to whole seconds (FT_MEDIA_LENGTH's wire format is uint32), bit_rate is converted from bps to kbps (FT_MEDIA_BITRATE), and the first stream's codec_name wins (video for video containers, audio for audio-only). Failures emit debug log lines but never surface user-visible errors — a file the probe can't read still shares fine, just without media tags. Boolean return with out-param instead of std::optional so the module matches the existing tree's C++ style. Callers MUST run Probe() off the main thread; a subsequent commit extends SharedFileList's batch path to do so. Prep for amule-org#140 Phase B — advertising media metadata from own shared files to ed2k servers + Kad. * feat(sharing): publish FT_MEDIA_{LENGTH,BITRATE,CODEC} to ed2k + Kad The Kad publisher at kademlia/kademlia/Search.cpp:1422 already iterates the file's media tags — but gates the whole emit on `file->GetMetaDataVer() > 0`. GetMetaDataVer had been a longstanding stub returning a hardcoded 0 with a TODO comment, so the code path was dead: no ed2k client has ever seen media metadata from an aMule-shared file. The ed2k server publish path (CreateOfferedFilePacket) had a matching stub — a comment reading "There, we could add MetaData info, if we ever get to have that." that built its outgoing tag list without touching m_taglist for media entries. Un-stub both: - GetMetaDataVer now derives from FT_MEDIA_LENGTH tag presence. No new persisted field: MediaProbe is the only source of the tag, and the tag itself already rides through known.met via the existing m_taglist load/save. Non-zero length is the "we've probed and have data worth publishing" signal, which is exactly what Kad's gate needs. - CreateOfferedFilePacket appends FT_MEDIA_LENGTH / FT_MEDIA_BITRATE as CTagVarInt (VBT-encoded for capable eMule clients + TYPETAG- INTEGER-capable servers, fixed 32-bit otherwise) and FT_MEDIA_CODEC as CTagString when the corresponding tag is present and non-empty / non-zero. Each tag is optional per file; a file with only length publishes only length. Prep for amule-org#140 Phase B — once MediaProbe wiring lands in SharedFileList's share-add path, tags flow to peers automatically. No change to known.met format (m_taglist already covers the persistence layer). * feat(prefs): [MediaMetadata]/Enabled + FFProbePath + UI panel Preferences plumbing for amule-org#140's ffprobe subprocess. Cfg items live in a fresh /MediaMetadata/ config namespace (won't clash with any /eMule/ key) with Enabled default false — an upgraded install won't kick off background probing until the user opts in from Preferences -> Files. UI panel goes at the bottom of PreferencesFilesTab: an Enable checkbox plus a "Path to ffprobe:" row with text field, Browse and Detect buttons. Browse routes through the existing OnButtonBrowseApplication switch (same file-selector infra the video-player / browser fields use). Detect fires MediaProbe::AutoDetectPath() and either populates the field or shows a friendly "install ffmpeg or Browse manually" info dialog. The whole box hides in amulegui via PrefsUnifiedDlg's amuledOnlyPrefs[] — probing runs daemon-side, the remote GUI has no business setting the path. IDC IDs sit in the 10420-10423 functional band and 10370 orphan- label slot, chosen to leave a 10-ID cushion above bind-to- interface's IDC_INTERFACE = 10410 so parallel branches can grow without collision. MediaProbe moves to COMMON_SOURCES so the Detect button's handler still links in remote-GUI builds (the button is hidden there but the event-table binding still needs the symbol). Follow-ups on the same branch: SharedFileList wiring (probe on share-add + known.met load), extension gate, background-probe throttling. * feat(shared): probe media metadata off-main + attach to CKnownFile Closes the amule-org#140 wiring loop: shared audio / video files now get probed with ffprobe, and the resulting FT_MEDIA_LENGTH / _BITRATE / _CODEC tags are attached to the CKnownFile so the earlier ed2k + Kad publisher un-stubs pick them up. Threading + throttling: - New CMediaProbeTask (ETP_Low) rides on the existing CThreadScheduler queue. That queue serialises tasks — a large library at first-boot-after-upgrade retrofits one file at a time, never stepping on hashing or completion which run at higher priority. No custom thread pool. - Task ctor snapshots the ffprobe path so the worker never touches thePrefs, and remembers just the file's CMD4Hash + CPath. Result marshals back via CMediaProbeEvent (new MULE_EVT_MEDIA_PROBE), main thread resolves the hash to a live CKnownFile via CKnownFileList::FindKnownFileByID (the file may have been unshared while we were probing) and calls AddTagUnique for each populated field, MarkECChanged, and knownfiles->Save so the tags survive a crash. Gating: - Preference /MediaMetadata/Enabled must be true AND FFProbePath non-empty. - Extension gate: only ED2KFT_AUDIO or ED2KFT_VIDEO files (from GetED2KFileTypeID) — skips the mass of docs / archives / images a typical share tree carries. - Already-probed gate: skip when FT_MEDIA_LENGTH > 0. This is the retrofit cache — once a file has been probed successfully its tag rides through known.met and future launches skip re-probing. Hook site is CSharedFileList::AddFile — the single choke point for both runtime share-adds (user picks a new folder / drops a file in Incoming) AND Reload()'s known.met walk, so an upgraded install retrofits every existing shared file on the first launch after the user enables the feature. No separate load-vs-add code path. Event-table bindings for MULE_EVT_MEDIA_PROBE landed in both amuled.cpp (daemon) and amule-gui.cpp (monolithic / remote GUI) so the handler fires regardless of which app the scheduler dispatched from. * chore(clang-format): apply clang-format-18 to amule-org#140 files * i18n(pot): regenerate catalogs for amule-org#140 UI additions Rerun of scripts/update-po.sh on top of the post-amule-org#281 (bind-to- interface) master so the Media metadata strings coexist with the new bind-to-interface strings. Replaces the earlier standalone regen that was skipped during the rebase. * feat(prefs): gray out ffprobe path controls when Media metadata is off The [MediaMetadata] Enabled checkbox now drives the enabled state of IDC_MEDIAMETA_FFPROBEPATHTEXT / _FFPROBEPATH / _FFPROBEBROWSE / _FFPROBEDETECT via the existing OnCheckBoxChange handler, and the initial state is applied at dialog open. Previously the path input, Browse and Detect buttons stayed live-looking with the feature disabled, which read as "I can configure ffprobe here" while nothing downstream would ever run.
got3nks
added a commit
that referenced
this pull request
Aug 7, 2026
…els (#847) * feat(gui): show free disk space on the Downloads and Shared Files panels The Downloads panel now reports the free space on the filesystem holding the part files beside the queue size, and turns red once that space no longer covers what is left to download. The Shared Files panel reports the free space where finished downloads land. Requested in #757. Which filesystem, and what to compare against, both follow from how the directories actually work. There is one temp directory for every category -- a category chooses where a file lands when it finishes, not where it downloads -- so the figure is global and the warning is measured against the whole queue rather than the category on screen; a per-category comparison would only fire once it was already too late. The threshold uses the bytes still to download, not the total queue size: the bytes a part file already holds are off the free-space figure already, so what is left is exactly what the disk still has to find room for. Incoming, by contrast, is per category, and the Shared Files panel has no category selector, so it reports the default category's -- and carries no threshold, since nothing there stops when the disk fills. Only the core can answer either question: the GUI may be on another machine entirely, and even where it mounts the same share it can see a different size or quota. Two new stats tags carry the figures over EC. An absent tag means unknown rather than zero, so a daemon older than these tags leaves the field empty instead of reporting a full disk. Both getters are cache-backed, re-sampling at most every ten seconds. statvfs()/GetDiskFreeSpaceEx() blocks on the directory, and on a network mount -- temp and incoming commonly are one -- a slow or stale server blocks it for as long as the mount's timeout. The callers are a per-second GUI refresh and every stats poll from every connected client, so an uncached read would multiply that exposure by the poll rate. A path that cannot be queried at all reports FREE_SPACE_UNKNOWN, which empties the label rather than printing "0 bytes" and never colours it red. The GUI side is gated on what is actually on screen: the timer refreshes only the panel currently displayed, and nothing at all while the window is hidden to the tray or minimized. That matters because the Downloads refresh walks the whole queue to decide whether to warn -- skipped outright when there is no figure to compare against -- and because each label is resolved by name, which is now done once and cached rather than per tick. The Shared Files statistics box grows from four columns to five: the collapse/expand button keeps a narrow column of its own and the size figures get a column beside it, one figure per row, so the total size lines up with the counters and the free space with the gauges. One new translatable string, "Free space: %s", shared by both panels; catalogs regenerated. The separator between the queue size and the free space is built in code so translators are given the figure alone. Builds clean on macOS (amule + amulegui + amuled); clang-format and both clang-tidy tiers clean over the diff. * perf(core): probe free space on its own thread, off the main loop CStatistics::GetTempFreeSpace() / GetIncomingFreeSpace() called CPath::GetFreeSpaceAt() inline, cached for ten seconds. Both callers are latency critical: the GUI timer on the main thread, and the EC stats reply on amuled's core thread. statvfs() / GetDiskFreeSpaceEx() blocks on the directory it is asked about. Measured read-only on a live host, 50 samples per path: ext4 0.002 ms median, mergerfs 0.678 ms, nfs4 0.204-0.222 ms, worst case 1.765 ms. So the healthy cost was never the problem, and the interval was not either. What those numbers do not cover is the pathological case: a cold autofs mount takes tens to hundreds of milliseconds, and an unreachable server blocks for timeo x retrans on a soft mount and indefinitely on a hard one. Either directory is commonly a network mount, and that stall landed on the main loop -- freezing the GUI and stalling EC replies. Nothing else in the core reaches those filesystems from the main loop: downloads write through CPartFileWriteThread, uploads read through CUploadDiskIOThread. This probe was the one exception. CFreeSpaceThread is a dedicated joinable worker rather than a task on CThreadScheduler, for the reason CMediaProbeThread was split out (#280): the scheduler runs one task at a time and owns completion, allocation, hashing, verification and IP filtering, so a probe blocked on a hung mount would not delay that queue but stop it -- and would couple unrelated filesystems, a hung incoming mount blocking the hashing of part files on a healthy local disk. Here a hung mount can only ever delay the next sample: the figure goes stale, the label empties, nothing else notices. The two figures become std::atomic<sint64>, written by the worker and read by the main and EC threads; the getters are plain relaxed loads that cannot block, so no caller changed. The worker wakes every second and samples each path only when its own interval has elapsed -- separately timed, so a slow incoming cannot hold temp back -- and the wake interval is shorter than the sample interval so shutdown does not wait out a sleeping thread. The worker reads no preferences. The temp and incoming paths can change under the preferences dialog, no worker in the tree touches thePrefs, and a wxString read while another thread assigns it is a race whatever the value; so CamuleApp hands the worker mutex-guarded copies, at construction and on each core tick. That also removes any question of teardown ordering against thePrefs. Constructed alongside mediaProbeThread, which is post-fork so the POSIX threads belong to the daemon child (#849), and torn down beside it in OnExit(). The panel-visibility gate stays, but it now skips pointless work rather than a blocking call: CDownloadListCtrl::UpdateFreeSpace() still walks the queue to decide whether to warn. Refreshing when a panel becomes active as well as on the timer means switching to a panel no longer shows the figure it had when it was last visible until the next tick.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #140.
Phase B of the media-metadata work — aMule now extracts Length / Bitrate / Codec from local shared audio and video files with
ffprobeand advertises them viaFT_MEDIA_LENGTH/FT_MEDIA_BITRATE/FT_MEDIA_CODECto eD2K servers and Kad, so other clients see populated columns when they search and find our files. Phase A (consumer-side rendering) already landed in #139.Why this shape
The issue laid out four backend options. Ships with
ffprobesubprocess as recommended:Two longstanding stubs un-stubbed
Both publishers had TODO stubs that were never wired. The whole feature had to un-stub them:
Search.cpp:1422gates the media-tag emit onfile->GetMetaDataVer() > 0.GetMetaDataVer()was a hardcodedreturn 0since forever (KnownFile.h:282,// TODO: This must be implemented if we ever want to have metadata.). Replaced with a derived getter that returns 1 when the file carries a non-zeroFT_MEDIA_LENGTH— no separate persisted field, tag presence is the signal.KnownFile.cpp:1242had the comment// There, we could add MetaData info, if we ever get to have that.and built its outgoing tag list from hardcoded fields. Now appendsFT_MEDIA_LENGTH/_BITRATE(VBT-encoded for capable clients / servers, fixed 32-bit otherwise) /_CODECfromm_taglistwhen present.Threading + throttling
CMediaProbeTaskrides the existingCThreadScheduleratETP_Low— hashing and file-completion tasks always preempt.thePrefs.MULE_EVT_MEDIA_PROBE— worker never touches CKnownFile state.ED2KFT_AUDIO/ED2KFT_VIDEOfiles (viaGetED2KFileTypeID) get scheduled.FT_MEDIA_LENGTH > 0— each file probed at most once per lifetime.CSharedFileList::AddFileis the single choke point every shared file passes through. So the same code path handles both:known.met, each entry re-entersAddFile, eligible files get retrofitted in the background.Startup is not deferred:
AddTaskis O(log N) enqueue; the actualffprobesubprocess runs on the scheduler worker thread afterOnInitreturns. A 10 k-file library retrofit is ~100 ms of enqueue overhead on the main thread plus ~10-15 min of background probing at 50-100 ms per file. Tags persist toknown.metper successful probe.Preferences
New
[MediaMetadata]section:UI panel added at the bottom of Preferences → Files: Enable checkbox, path field, Browse and Detect buttons. The whole block hides in
amulegui(probing runs daemon-side).AutoDetectPath()is a two-step probe:ffprobe -versionon PATH first, then a per-platform well-known-paths fallback (Homebrew + MacPorts on macOS; Chocolatey + Scoop +C:\ffmpeg\binon Windows; distro prefixes +/snap/binon Linux + OpenBSD). Falls back to well-known paths because GUI-launched processes on macOS lose the shell PATH.Verification
Smoke-tested end-to-end on macOS: dropped a 5-second 96 kbps sine-wave MP3 into the shared Incoming dir,
SharedDirWatcherpicked it up → hashed →AddFilescheduled probe →ffprobereturned duration + bit_rate + codec → main-thread handler attached tags. Read back byte-for-byte fromknown.met:FT_MEDIA_LENGTH= 5 secondsFT_MEDIA_BITRATE= 97 kbpsFT_MEDIA_CODEC= "mp3"Wire format is standard ed2k tag encoding (UINT32 for length/bitrate, STRING for codec), matches the format Phase A already reads.
Notes for reviewers
known.metload works via the existingm_taglistload/save — no known.met format bump needed.