Skip to content

EC: cache the full GET_SHARED_FILES / GET_DLOAD_QUEUE responses for FULL fetches - #731

Closed
got3nks wants to merge 9 commits into
amule-project:masterfrom
got3nks:feat/ec-full-response-cache-on-727
Closed

EC: cache the full GET_SHARED_FILES / GET_DLOAD_QUEUE responses for FULL fetches#731
got3nks wants to merge 9 commits into
amule-project:masterfrom
got3nks:feat/ec-full-response-cache-on-727

Conversation

@got3nks

@got3nks got3nks commented May 26, 2026

Copy link
Copy Markdown
Contributor

Summary

amulecmd's show shared and show DL (and any other EC_OP_GET_SHARED_FILES / EC_OP_GET_DLOAD_QUEUE request issued at EC_DETAIL_FULL with no EC_TAG_KNOWNFILE / EC_TAG_PARTFILE queryitems) iterate the full file list and rebuild every per-file tag tree from scratch on every invocation. On a 91k-file shareset that's the ~10 s of CPU per amulecmd show shared invocation profiled in #713.

The work is fully redundant when the shareset hasn't changed since the previous request — which is the common case for amulecmd, since each invocation is a fresh short-lived EC connection with no client-side state to incrementalize against.

This PR adds a daemon-wide cache keyed on CKnownFile::GetGlobalECGen() (the process-wide change-generation counter introduced by #727):

  • One CECFullResponseCache instance per opcode (shared files, download queue). Each wraps a builder function that constructs a self-contained CECPacket from the current daemon state.
  • Get() returns a shared_ptr<const CECPacket>. If s_globalEcGen matches the cached generation, returns the cached packet immediately; otherwise rebuilds outside the lock and stores under a short critical section.
  • Both EC_OP_GET_SHARED_FILES and EC_OP_GET_DLOAD_QUEUE handlers check the fast path first: detail == EC_DETAIL_FULL && queryitems is emptySendPacket the cached tree and return NULL from ProcessRequest2 (skipping the framework's double-send). Anything else (EC_DETAIL_UPDATE, EC_DETAIL_INC_UPDATE, or any queryitems populated by amuleweb's phase-3 follow-up) stays on the existing per-connection path.

Expected impact

  • show shared on a 91k library: ~10 s → <100 ms in steady state. First request after any per-file change pays the rebuild cost (~10 s once), subsequent requests are cached again.
  • show DL: cache hit on idle / paused queues and back-to-back invocations. With active downloads CPartFile::Process() ticks s_globalEcGen ~1 Hz so the cache invalidates frequently, but the overhead is still bounded by at-most-one rebuild per cycle.

Bypassed paths

  • EC_DETAIL_UPDATE — amuleweb / amulegui INC_UPDATE, per-connection diffs by definition.
  • Requests with queryitems populated — amuleweb phase-3 follow-up for newly discovered IDs, small filtered subset, not the hot path.

Memory cost

  • ~30 MB resident for the shared-files cache on a 91k library.
  • A few KB for the download-queue cache.

Single copy daemon-wide, refcounted via shared_ptr; freed automatically when superseded by a rebuild.

Dependencies

Depends on #727 for CKnownFile::GetGlobalECGen() and the MarkECChanged() hooks that drive its invalidation. Built on top of feat/ec-skip-unchanged-files.

Test plan

  • macOS Debug build of amuled, amulegui, amulecmd, amuleweb — clean compile.
  • Loopback smoke test: small shareset, amulecmd show shared first call builds the cache (~0.43 s), second call hits (~0.085 s). Same for show DL.
  • Cache invalidation: drop a new file into the shared dir, reload shared, next show shared rebuilds correctly (4 files vs prior 3). Subsequent calls hit the new cache.
  • amulegui still runs through the per-connection INC_UPDATE path (Partial update: yes, INC responses 100-1400 bytes; the cache code is bypassed).
  • @Stoatwblr profile run on the 91k-file shareset: time amulecmd -c "show shared" should drop from ~10 s to <1 s in steady state.

Refs #713, depends on #727.

got3nks added 9 commits May 26, 2026 13:57
Adds the data + plumbing for skipping unchanged files in INC_UPDATE
responses (amule-project#713). No callers yet, no behavior change — pure
infrastructure to make the next two commits reviewable on their own.

Each CKnownFile carries an atomic uint64 generation counter. A process-
wide atomic global counter ensures every Mark…() yields a strictly-
ascending value across all files and all threads (CFileStatistic
counters tick from upload-disk-IO + hashing threads, not just main).

`MarkECChanged()` uses a single fetch_add(1) + store rather than a load /
increment / store so concurrent mutations from different threads can't
collide on the same gen.

Memory cost: 8 bytes per CKnownFile (~700 KB for a 91 k-file shareset).
One additional atomic uint64 at file scope.

Refs amule-project#713.
Wires `MarkECChanged()` into each storage primitive that writes a field
exported via `CEC_SharedFile_Tag` / `CEC_PartFile_Tag`. Still no behavior
change on the EC dispatch side — the next commit consumes the flag.

Sites covered:

  * `CFileStatistic::AddRequest / AddAccepted / AddTransferred`
    (EC_TAG_KNOWNFILE_REQ_COUNT, _ACCEPT_COUNT, _XFERRED and their _ALL
    counterparts). `AddTransferred` is the hottest of the three —
    every uploaded chunk flips the flag. The Mark op is a single
    atomic fetch_add+store so the cost is small even on multi-slot
    upload nodes.
  * `CKnownFile::AddUploadingClient / RemoveUploadingClient`
    (EC_TAG_KNOWNFILE_ON_QUEUE = m_ClientUploadList.size()).
  * `CKnownFile::SetFileName` (EC_TAG_PARTFILE_NAME and ed2k:// link).
  * `CKnownFile::SetFilePath` (EC_TAG_KNOWNFILE_FILENAME on non-partfiles).
  * `CKnownFile::SetUpPriority / SetAutoUpPriority` (EC_TAG_KNOWNFILE_PRIO,
    which folds the auto flag in as +10).
  * `CKnownFile::SetFileCommentRating` (EC_TAG_KNOWNFILE_COMMENT / _RATING).
  * `CKnownFile::UpdatePartsInfo` (EC_TAG_KNOWNFILE_COMPLETE_SOURCES{,LOW,HIGH}).
  * `m_pAICHHashSet->SetMasterHash` call sites in KnownFile.cpp /
    PartFile.cpp and `SetAICHHashset` swap in amule.cpp's hash-task
    completion (EC_TAG_KNOWNFILE_AICH_MASTERHASH).
  * `CPartFile::Process` — coarse hook at the per-second per-partfile
    tick. Partfiles have ~20 EC-exported fields that change
    independently on active downloads; the per-second tick is the
    right granularity and avoids hooking each of them.

A 60 s periodic backstop in commit (3/3) catches anything missed here.

Refs amule-project#713.
Consumes the per-file `m_ecGen` from commit (1/3) and the `MarkECChanged`
hooks from commit (2/3): `Get_EC_Response_GetUpdate` now skips files
whose generation is at or below the highest gen this connection has
already reflected in its `CObjTagMap`. For a 91 k-file shareset with a
few hundred actively-transferring files at any moment, the per-cycle
iteration goes from O(N) to O(files-that-actually-changed) — typically
a 99 %+ reduction.

Mechanism per connection:

  * snapshot = CKnownFile::s_globalEcGen at request start
  * threshold = m_lastEcGenSeen carried from the previous response
  * iterate only files where file.m_ecGen > threshold
  * m_lastEcGenSeen := snapshot after iteration
  * files whose m_ecGen changes mid-iteration land above the snapshot
    and get picked up on the next request

Backstop: every kFullEcSyncIntervalSeconds (60 s) every connection
runs an unconditional full sweep regardless of gen. Worst-case
staleness if a `MarkECChanged()` hook was missed somewhere is bounded
to that interval rather than indefinite. The interval is short enough
to keep amulegui's UI from drifting noticeably and long enough that
the 99 % skip win still dominates the per-cycle workload.

Wire format unchanged; INC_UPDATE responses now contain only the
files that changed since the last response, which is exactly what the
delta protocol was designed to convey but the implementation used to
emit empty tag bodies for unchanged files anyway. Backwards-compatible
with any client version (the client side already correctly handles
missing-file-tag = "no change").

Refs amule-project#713.
…t-in

Commit 3 broke amulegui on big libraries because amulegui infers
deletions from absence: any file ID missing from an INC_UPDATE response
is removed from the local view. Skipping unchanged files entirely meant
most of the shareset disappeared every cycle (then the 5-min backstop
re-added them), wedging the GUI's list-widget at 100% CPU in
std::unordered_map churn.

Two-pronged fix preserving the optimization in both compat and opt-in
paths:

* Backward compat (old amulegui / amuleweb / third-party client):
  unchanged files get a 5-byte "alive marker" tag — EC_TAG_KNOWNFILE /
  EC_TAG_PARTFILE with the ECID and no children. The legacy client
  already gates updates on `if (tag->HasChildTags())`, so empty tags
  count as "present" without triggering the per-item update. Keeps the
  expensive CEC_SharedFile_Tag construction + encoder pass off the
  hot path (the actual perf win); only pays a tiny per-file wire cost.

* Partial-update protocol opt-in:
  - New EC_TAG_CAN_PARTIAL_UPDATE capability advertised by the client
    at auth, mirrored back by the server in AUTH_OK if it understands
    the protocol.
  - When negotiated, server skips unchanged files entirely and emits
    explicit EC_TAG_FILE_REMOVED markers (top-level, value = ECID)
    for files that disappeared since the previous cycle.
    CECServerSocket tracks the previous cycle's snapshot in
    m_lastSentFileIds.
  - amulegui clients in this mode skip the bulk "missing == deleted"
    loop and process explicit removals only.

Also drops the 5-minute paranoia backstop. With explicit removal
markers and alive markers covering both protocol paths, the backstop's
only remaining job was hiding missed MarkECChanged() hooks — making
those bugs harder to find rather than easier.

Negotiation matrix:
  new server + new client → partial-update (full win, ~0 work
    per-cycle for static files)
  new server + old client → alive markers (almost full win, cheap
    per-file wire cost)
  old server + new client → server ignores capability, doesn't mirror
    it back; client falls back to bulk-deletion path
  old server + old client → unchanged
amuleweb drives its INC_UPDATE polling through `EC_OP_GET_SHARED_FILES`
+ `EC_OP_GET_DLOAD_QUEUE` with `EC_DETAIL_UPDATE` rather than the
`EC_OP_GET_UPDATE` path amulegui uses. Commit (4/5) wired the partial-
update protocol only into `Get_EC_Response_GetUpdate`, so amuleweb
kept iterating all files every cycle even on huge libraries.

Extend the same skip-unchanged + `EC_TAG_FILE_REMOVED` logic into
`Get_EC_Response_GetSharedFiles` and `Get_EC_Response_GetDownloadQueue`,
guarded on the same `m_partialUpdateActive` flag and only kicking in
for `EC_DETAIL_UPDATE` requests (so amulecmd's `show shared` and other
`EC_DETAIL_FULL` callers are untouched).

Per-path state because the three handlers fire on independent cadences:

* `m_lastEcGenSeen`        + `m_lastSentFileIds`        — amulegui's
  `EC_OP_GET_UPDATE`
* `m_lastEcGenSeenShared`  + `m_lastSentSharedFileIds`  — amuleweb's
  `EC_OP_GET_SHARED_FILES`
* `m_lastEcGenSeenPart`    + `m_lastSentPartFileIds`    — amuleweb's
  `EC_OP_GET_DLOAD_QUEUE`

amuleweb's `UpdatableItemsContainer::ProcessUpdate` learns the same
two-path behaviour: when the negotiated capability is active, drain
`EC_TAG_FILE_REMOVED` markers into a buffered removal set and run a
single sweep over the live list only when removals exist; otherwise
the legacy "missing == deleted" loop stays correct (server still emits
diff-empty alive-marker tags via the existing encoder path).

`CaMuleExternalConnector::IsServerPartialUpdateActive()` exposes
`CRemoteConnect::ServerSupportsPartialUpdate()` to the container.

Compat matrix unchanged from (4/5): the negotiation is symmetric, so
old amuleweb + new amuled and new amuleweb + old amuled both fall back
to the legacy path automatically.
…ction setters

Reverse audit of every EC_DETAIL_UPDATE field surfaced nine mutation
sites that change exported state without marking the partfile changed.
For an active partfile the per-tick `MarkECChanged()` at the top of
`CPartFile::Process()` covered them implicitly, but `DownloadQueue::
Process()` gates `Process()` to `PS_READY`/`PS_EMPTY` only — paused,
stopped, hashing, and completed partfiles never auto-mark, so a user
toggling pause/resume/priority/category from amulegui or amuleweb
would stay invisible until something else on the file changed.
The 5-minute paranoia backstop (removed in 4/5) used to paper over
this; explicit hooks are the correct fix.

Sites covered:

* `CPartFile::StopFile`              — EC_TAG_PARTFILE_STOPPED / _STATUS
* `CPartFile::PauseFile`             — EC_TAG_PARTFILE_STOPPED / _STATUS
* `CPartFile::ResumeFile`            — EC_TAG_PARTFILE_STOPPED / _STATUS
* `CPartFile::SetDownPriority`       — EC_TAG_PARTFILE_PRIO
* `CPartFile::SetAutoDownPriority`   — EC_TAG_PARTFILE_PRIO (auto flag
                                        folded in via +10 offset)
* `CPartFile::SetA4AFAuto`           — EC_TAG_PARTFILE_A4AFAUTO
* `CPartFile::SetCategory`           — EC_TAG_PARTFILE_CAT
* `CPartFile::SetHashingProgress`    — EC_TAG_PARTFILE_HASHED_PART_COUNT
                                        (runs during hashing, outside
                                        Process()'s gate)
* `CPartFile::UpdateFileRatingCommentAvail` — EC_TAG_PARTFILE_COMMENTS
                                        (fires when sources deliver
                                        ratings/comments via OP_MESSAGE)

All hooks are conditional on actual value change to keep the
generation counter from incrementing on no-op writes.
…ULL fetches

amulecmd's `show shared` and `show DL` (and any other
`EC_OP_GET_SHARED_FILES` / `EC_OP_GET_DLOAD_QUEUE` request issued at
`EC_DETAIL_FULL` with no `EC_TAG_KNOWNFILE` / `EC_TAG_PARTFILE`
queryitems) iterate the full file list and rebuild every per-file tag
tree from scratch on every invocation. On a 91 k-file shareset that's
the ~10 s of CPU per amulecmd `show shared` invocation profiled in
issue amule-project#713.

The work is fully redundant when the shareset hasn't changed since the
previous request — which is the common case for amulecmd, since each
invocation is a fresh short-lived EC connection with no client-side
state to incrementalize against.

This commit adds a daemon-wide cache keyed on `CKnownFile::
GetGlobalECGen()` (the process-wide change-generation counter
introduced by PR amule-project#727):

  * One CECFullResponseCache instance per opcode (shared files,
    download queue). Each wraps a builder function that constructs a
    self-contained CECPacket from the current daemon state.
  * `Get()` returns a shared_ptr<const CECPacket>. If `s_globalEcGen`
    matches the cached generation, returns the cached packet
    immediately; otherwise rebuilds outside the lock and stores under
    a short critical section.
  * Both EC_OP_GET_SHARED_FILES and EC_OP_GET_DLOAD_QUEUE handlers
    check the fast path first: `detail == EC_DETAIL_FULL && queryitems
    is empty` -> SendPacket the cached tree and return NULL from
    ProcessRequest2 (skipping the framework's double-send). Anything
    else (EC_DETAIL_UPDATE, EC_DETAIL_INC_UPDATE, or any queryitems
    populated by amuleweb's phase-3 follow-up) stays on the existing
    per-connection path.

Expected impact:

  * `show shared` on a 91 k library: ~10 s -> <100 ms in steady state.
    First request after any per-file change pays the rebuild cost
    (~10 s once), subsequent requests are cached again.
  * `show DL`: cache hit on idle / paused queues and back-to-back
    invocations. With active downloads CPartFile::Process() ticks
    `s_globalEcGen` ~1 Hz so the cache invalidates frequently, but the
    overhead is still bounded by at-most-one rebuild per cycle.

Bypassed paths:

  * EC_DETAIL_UPDATE: amuleweb / amulegui INC_UPDATE. Per-connection
    diffs by definition; not cacheable cross-connection.
  * Requests with queryitems populated: amuleweb phase-3 follow-up
    for newly discovered IDs, asking for a small filtered subset.

Memory cost: ~30 MB resident for the shared-files cache on a 91 k
library; a few KB for the download-queue cache. Single copy daemon-
wide, refcounted; freed automatically when superseded by a rebuild.

Depends on amule-project#727 for `CKnownFile::GetGlobalECGen()` and the
MarkECChanged hooks that drive its invalidation.

Refs amule-project#713.
@got3nks got3nks closed this May 26, 2026
got3nks added a commit to got3nks/amule that referenced this pull request May 27, 2026
…EUE paths

amulecmd's `show shared` and `show DL` (and any other
`EC_OP_GET_SHARED_FILES` / `EC_OP_GET_DLOAD_QUEUE` request issued at
`EC_DETAIL_FULL` with no `EC_TAG_KNOWNFILE` / `EC_TAG_PARTFILE`
queryitems) iterate every file and rebuild every per-file tag tree
from scratch on every invocation. On a 91k-file shareset that's the
~10 s of CPU per amulecmd `show shared` invocation profiled in issue
amule-project#713 — and the work is fully wasted when the shareset hasn't changed
since the previous request.

This adds a daemon-wide per-file bytes cache keyed off
`CKnownFile::s_globalEcGen` (the change-generation counter from the
INC_UPDATE work). Per file the cache holds one pre-serialized wire-
format blob, freshness-stamped with the file's `m_ecGen` at build
time. On each request:

  * Snapshot the current shareset / download queue.
  * For each file: reuse the cached blob if its gen still matches;
    otherwise build a fresh CEC_SharedFile_Tag / CEC_PartFile_Tag,
    serialize it via the new CECMemSocket, and store. Only the
    entries whose file gen advanced since the last request are
    rebuilt (the same per-file freshness primitive INC_UPDATE uses).
  * Concatenate the per-file blobs and feed them through the
    connection's socket via SendCachedBodyResponse, which mirrors
    WritePacket's flag-byte / length-header / per-connection deflate
    dance from WriteBuffer outward — so the per-connection ZLIB-
    bypass on local peers still applies to the concatenated stream.

Bypassed paths (fall through to the existing live builders):

  * EC_DETAIL_UPDATE — amuleweb / amulegui INC_UPDATE. Per-connection
    diff state by definition, not shareable.
  * Requests with queryitems populated — amuleweb's phase-3 follow-up
    for newly discovered IDs, asking for a small filtered subset.
  * Connections that didn't negotiate EC_FLAG_UTF8_NUMBERS +
    EC_FLAG_LARGE_TAG_COUNT (the wire format the cached blobs
    assume). Every modern client advertises both at auth; very old
    clients drop to the live path automatically.

New EC-library pieces:

  * CECMemSocket — a CECSocket subclass that captures all I/O into
    an in-memory vector. Used at build time to serialize one tag
    into a self-contained byte blob.
  * CECSocket::SendCachedBodyResponse — public helper that emits a
    precomputed body (opcode + child-count + concatenated blobs)
    through the existing socket buffer / compression machinery.
  * CECTag::Serialize — public wrapper around the protected WriteTag
    primitive (was already the right shape for serializing a single
    self-contained tag).
  * CECSocket::SetTxFlags — protected setter so daemon-internal
    subclasses can pre-set the per-packet wire format before invoking
    WriteBuffer / WriteNumber directly (without going through
    WritePacket).
  * `friend class CECMemSocket` on CECSocket so the mem sink can call
    FlushBuffers / OnOutput to drain the in-flight buffer chain into
    its capture vector.

Memory cost: ~30 MB resident on a 91k library for the shared-files
cache; a few KB for the download queue. Single copy daemon-wide,
shared_ptr-refcounted; orphan entries pruned after each request via
CECFullResponseCache::PruneOutsideOf.

Cache invalidation rate: tracks s_globalEcGen, which bumps on every
per-file change including chunk transfers. For idle / mostly-paused
daemons the cache holds; busy seeders rebuild many per-file entries
per second but at most one rebuild per file per change. The per-file
granularity (vs the earlier whole-blob cache attempt in amule-project#731) means
concurrent activity on one file doesn't invalidate the cached blobs
for the other 90,999 files in the shareset.

Local smoke test (3-file shareset):

  * `show shared` first call: 0.446 s (cold cache build).
  * `show shared` subsequent: 0.079 s (cache hit).
  * Add file, reload, `show shared`: new file present, cache adapts.
  * Delete file, reload, `show shared`: file gone, prune drops it.
  * amulegui INC_UPDATE path stays at <5 % CPU (cache bypassed
    correctly).

Refs amule-project#713.
mrjimenez pushed a commit that referenced this pull request May 27, 2026
…EUE paths

amulecmd's `show shared` and `show DL` (and any other
`EC_OP_GET_SHARED_FILES` / `EC_OP_GET_DLOAD_QUEUE` request issued at
`EC_DETAIL_FULL` with no `EC_TAG_KNOWNFILE` / `EC_TAG_PARTFILE`
queryitems) iterate every file and rebuild every per-file tag tree
from scratch on every invocation. On a 91k-file shareset that's the
~10 s of CPU per amulecmd `show shared` invocation profiled in issue
#713 — and the work is fully wasted when the shareset hasn't changed
since the previous request.

This adds a daemon-wide per-file bytes cache keyed off
`CKnownFile::s_globalEcGen` (the change-generation counter from the
INC_UPDATE work). Per file the cache holds one pre-serialized wire-
format blob, freshness-stamped with the file's `m_ecGen` at build
time. On each request:

  * Snapshot the current shareset / download queue.
  * For each file: reuse the cached blob if its gen still matches;
    otherwise build a fresh CEC_SharedFile_Tag / CEC_PartFile_Tag,
    serialize it via the new CECMemSocket, and store. Only the
    entries whose file gen advanced since the last request are
    rebuilt (the same per-file freshness primitive INC_UPDATE uses).
  * Concatenate the per-file blobs and feed them through the
    connection's socket via SendCachedBodyResponse, which mirrors
    WritePacket's flag-byte / length-header / per-connection deflate
    dance from WriteBuffer outward — so the per-connection ZLIB-
    bypass on local peers still applies to the concatenated stream.

Bypassed paths (fall through to the existing live builders):

  * EC_DETAIL_UPDATE — amuleweb / amulegui INC_UPDATE. Per-connection
    diff state by definition, not shareable.
  * Requests with queryitems populated — amuleweb's phase-3 follow-up
    for newly discovered IDs, asking for a small filtered subset.
  * Connections that didn't negotiate EC_FLAG_UTF8_NUMBERS +
    EC_FLAG_LARGE_TAG_COUNT (the wire format the cached blobs
    assume). Every modern client advertises both at auth; very old
    clients drop to the live path automatically.

New EC-library pieces:

  * CECMemSocket — a CECSocket subclass that captures all I/O into
    an in-memory vector. Used at build time to serialize one tag
    into a self-contained byte blob.
  * CECSocket::SendCachedBodyResponse — public helper that emits a
    precomputed body (opcode + child-count + concatenated blobs)
    through the existing socket buffer / compression machinery.
  * CECTag::Serialize — public wrapper around the protected WriteTag
    primitive (was already the right shape for serializing a single
    self-contained tag).
  * CECSocket::SetTxFlags — protected setter so daemon-internal
    subclasses can pre-set the per-packet wire format before invoking
    WriteBuffer / WriteNumber directly (without going through
    WritePacket).
  * `friend class CECMemSocket` on CECSocket so the mem sink can call
    FlushBuffers / OnOutput to drain the in-flight buffer chain into
    its capture vector.

Memory cost: ~30 MB resident on a 91k library for the shared-files
cache; a few KB for the download queue. Single copy daemon-wide,
shared_ptr-refcounted; orphan entries pruned after each request via
CECFullResponseCache::PruneOutsideOf.

Cache invalidation rate: tracks s_globalEcGen, which bumps on every
per-file change including chunk transfers. For idle / mostly-paused
daemons the cache holds; busy seeders rebuild many per-file entries
per second but at most one rebuild per file per change. The per-file
granularity (vs the earlier whole-blob cache attempt in #731) means
concurrent activity on one file doesn't invalidate the cached blobs
for the other 90,999 files in the shareset.

Local smoke test (3-file shareset):

  * `show shared` first call: 0.446 s (cold cache build).
  * `show shared` subsequent: 0.079 s (cache hit).
  * Add file, reload, `show shared`: new file present, cache adapts.
  * Delete file, reload, `show shared`: file gone, prune drops it.
  * amulegui INC_UPDATE path stays at <5 % CPU (cache bypassed
    correctly).

Refs #713.
ngosang added a commit to ngosang/amule that referenced this pull request Jul 31, 2026
…e-project#731)

The download detail's Comments tab re-fetched GET /downloads/{hash}/comments
on every tick of the downloads store, so an open tab on an active download
issued roughly one request per second. amuled already emits a comments_updated
SSE event whose payload is byte-for-byte that same body (EVENTS.md), and it was
simply not wired up on the client.

- events.js: subscribe to comments_updated and republish it on the
  "comments:updated" store key, matching the existing search:result /
  log:appended one-off event pattern.
- download-detail.js: DownloadComments now fetches once per hash and applies
  the event payload directly when its hash matches, so retrieved Kad notes and
  source-reported comments both land without polling. Because EqualComments is
  deliberately excluded from EqualDownload, notes arriving mid-search on an
  otherwise idle (paused/stopped) download previously stayed invisible until
  the lookup finished; they now show up as they arrive.
- The "Searching Kad…" button state reads kad_comment_search_running from the
  downloads store rather than from the comments body: the Comments tab pins
  liveTick to 0, so the detail object never refreshes while that tab is open,
  whereas the list item does (the flag is part of EqualDownload, so its
  start -> finish edge arrives as download_updated).

Verified against a running amuled: one GET /comments over a 90s open tab
(previously one per tick), the button flipping to "Searching Kad…" and back on
a real Kad lookup, and no console errors.
got3nks pushed a commit to got3nks/amule that referenced this pull request Jul 31, 2026
…r.cpp (amule-project#675) (amule-project#725)

* chore(gui): delete unreachable bitmap functions/entries from muuli_wdr.cpp

First slice of the icon-system cleanup scoped in amule-project#675: remove code with
zero call sites anywhere in the tree, before any wxArtProvider migration
work starts.

- muleToolbar(): whole function unused -- superseded by the main
  wxToolBar setup in amuleDlg.cpp; nothing calls it.
- moreImages(): whole function unused, both of its two icon entries.
- amuleDlgImages(): 21 of 35 index blocks have no caller anywhere
  (0-13, 16, 17, 19, 27, 28, 31, 34). The 14 live ones are untouched --
  10 of those (20-26, 29, 32, 33) are already the fallback path inside
  amuleDlg.cpp's Add_Skin_Icon, which prefers a wxArtProvider/SVG lookup
  first; the other 4 (14, 15, 18, 30) are still called directly.
- amuleSpecial(): 6 of 26 index blocks have no caller (6, 7, 8, 9, 18,
  20) -- checked both direct call sites and the PrefsUnifiedDlg.cpp
  fallback table (pages[].m_imageidx), which uses neither.

convert_xpm in PartFileConvertDlg.cpp was on the same "no literal
grep hits" list initially but is not actually dead -- SetIcon(wxICON
(convert)) reaches it via the wxICON macro's token-pasting
(X##_xpm), invisible to a plain identifier search. Caught by a full
build failing on the undeclared identifier, not by inspection; left
untouched.

Deletes 1373 lines (~16% of the file). No behavior change: every
touched entry was unreachable code. clang-format v18 clean; full
amule build verified (macOS, CLIENT_GUI unaffected since neither
touched symbol is CLIENT_GUI-only).

* chore(po): regenerate catalogs after muuli_wdr.cpp dead-code removal

muleToolbar() duplicated several msgid source-location references
(Networks, Searches, Downloads Window, etc.) already present via
amuleDlg.cpp's own toolbar setup. Deleting it drops those now-stale
#: comments; no msgid added, removed, or retranslated -- verified via
unchanged msgid count (1903 before/after) and a diff limited to
source-location comments and POT-Creation-Date.

Regenerated after rebasing onto upstream/master to pick up po/
changes from amule-project#723/amule-project#724/amule-project#726/amule-project#728/amule-project#730/amule-project#731, which had drifted our
prior regeneration out of sync.
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.

1 participant