Skip to content

RFC / Discussion: Fetch file comments & ratings over Kad ("Kad Notes") for search results and downloads — parity with eMule #434

Description

@ngosang

Status: idea / discussion, not a hard implementation request. This document
compares how ratings and comments work in aMule vs eMule, pinpoints what aMule is
missing, and sketches a full design (core + EC + REST + Web UI) as a basis for
discussion. All claims are backed by file:line references — aMule paths are
relative to the repo; eMule paths refer to the eMule source tree.

1. Summary / motivation

eMule can show comments and ratings for a file the user has not downloaded
you can right-click a search result, hit "Search Kad", and see comments/ratings
that other users published for that file. aMule cannot: its search results carry
only a weak self-rating, and comments are available only for files that are
already downloading (from connected sources).

The data eMule shows travels over Kademlia via a dedicated "Notes" lookup keyed
on the file hash (distinct from the keyword search that returns the file).
aMule already contains half of this machinery — it publishes notes and can
parse a notes response — but it never starts a notes lookup, never routes
results to search results, and never surfaces them in any UI.

This RFC proposes closing that gap and, since aMule's remote/Web interface is the
long-term consumer, exposing the result over EC → REST → Web UI.

2. Background: two different rating/comment channels

There are two independent channels, and it is important not to conflate them:

  1. Self-rating on the offered file (rides with the search hit). The client that
    offers a file may attach FT_FILERATING to its offered-file packet; an ed2k
    server relays it back with the search hit. This is a single publisher's own
    rating
    , not an aggregate. (Kad keyword search carries no rating at all in
    either client.)
  2. Kad Notes (comments + ratings, keyed on hash). Any client that has the file
    can publish a note (comment + rating) to the Kad nodes responsible for the file
    hash. Anyone can later retrieve all notes for that hash — without downloading
    the file or connecting to its sources.
    This is the channel eMule uses to show
    comments for non-downloaded files, and the subject of this RFC.

A third, unrelated channel is per-source comments for a file you are already
downloading (read from connected CUpDownClients) — aMule already has this for
downloads (CPartFile::GetRatingAndComments, src/PartFile.cpp:4202-4214).

3. How it works today in aMule

  • Self-rating publish: FT_FILERATING is written only in
    CKnownFile::CreateOfferedFilePacket (src/KnownFile.cpp:1226-1229), from the
    publisher's own GetFileRating(). A search hit parses it in
    src/SearchFile.cpp:82: m_iUserRating = (tag.GetInt() & 0xF) / 3 — quantized to
    0-3, and it is a single self-rating, never an aggregate.
  • Per-source comments (downloads only): CPartFile::GetRatingAndComments
    (src/PartFile.cpp:4202-4214) reads only m_SrcList — comments from connected
    sources. Requires the file to be a CPartFile with live sources.
  • Kad Notes — the half that exists:
    • Publish (STORENOTES): src/SharedFileList.cpp:1477-1499
      PrepareLookup(CSearch::STORENOTES, …); packet built in
      src/kademlia/kademlia/Search.cpp:817-843 (attaches TAG_FILENAME,
      TAG_FILERATING, TAG_DESCRIPTION, TAG_FILESIZE).
    • Parse response: CSearch::ProcessResultNotes
      (src/kademlia/kademlia/Search.cpp:1061-1110), keeping TAG_FILENAME,
      TAG_DESCRIPTION, TAG_FILERATING (:1078-1088); dispatched via
      KademliaUDPListener.cpp:313-314ProcessSearchNotesResponse:1474
      CSearchManager::ProcessResult (src/kademlia/kademlia/SearchManager.cpp:537).
  • Kad Notes — the half that is missing:
    • Retrieval is never triggered. There is no PrepareLookup(CSearch::NOTES, …)
      caller anywhere; no FindNoteFile; the CSearch::NOTES lifecycle branches
      (SearchManager.cpp:321,468) are dead code.
    • Results are not routed to search hits. ProcessResultNotes looks the hash
      up only in sharedfiles then downloadqueue and calls file->AddNote(entry);
      theApp->searchlist is never consulted and CSearchList has no AddNotes.
    • Nothing surfaces m_kadNotes. AddNote stores into
      CAbstractFile::m_kadNotes (src/KnownFile.cpp:295), but no code reads it back
      for display — not even the download comment view, which reads only m_SrcList.
    • Request builder is shared-file-bound. The NOTES request build (equivalent to
      eMule Search.cpp:604) resolves the file's size from the shared-file registry,
      which does not hold search-result files — a blocker for search-result use.

Net: the comment/rating data for a non-downloaded file exists on the Kad
network and aMule can already parse it, but aMule never asks for it, never attaches
it to a search result, and never displays it.

4. How eMule does it (the parts aMule needs)

Structural key: in eMule the entire notes API lives on the base class
CAbstractFile
— from which CKnownFile, CPartFile, and CSearchFile all
derive (AbstractFile.h:111-136: AddNote, getNotes,
IsKadCommentSearchRunning, m_kadNotes). So the same mechanism works uniformly
for shared files, downloads, and search results.

Full lifecycle:

  1. Trigger (user-initiated "Search Kad"): CommentDialog.cpp:313-336 and
    CommentDialogLst.cpp:176-199 call
    CSearchManager::PrepareLookup(CSearch::NOTES, true, CUInt128(hash)), then flag
    SetKadCommentSearchRunning(true). Guarded by
    CSearchManager::AlreadySearchingFor (SearchManager.cpp:259). Reachable from a
    search result: the context menu (SearchListCtrl.cpp:1019, handler
    :917-922) opens a detail sheet whose comment page is a CCommentDialogLst bound
    to the selected CSearchFile — so "Search Kad" runs a NOTES lookup on the search
    hit's hash.
  2. Request: KADEMLIA2_SEARCH_NOTES_REQ (Search.cpp:660-704).
  3. Response → route to all three collections: ProcessResultNotes
    (Search.cpp:1118-1233) builds a Kademlia::CEntry, then attaches it to:
    • search resultssearchlist->AddNotes(pEntry, id) (Search.cpp:1202
      SearchList.cpp:769-788, iterates CSearchFiles and matches by hash);
    • shared files and downloads (Search.cpp:1205-1212).
  4. Surface in UI:
    • CCommentDialogLst::RefreshData (CommentDialogLst.cpp:130-161) merges the
      per-source ed2k comments and file->getNotes(); CCommentListCtrl shows an
      Origin column ("eD2K" vs "Kad", CommentListCtrl.cpp:216,230-238).
    • The search list draws a rating smiley plus a "has comment" overlay plus a
      "Kad search running" spinner (SearchListCtrl.cpp:1421-1425);
      CSearchFile::UpdateFileRatingCommentAvail (SearchFile.cpp:332-361) computes
      HasComment and an averaged rating from m_kadNotes.

Secondary detail — aggregate rating fidelity: eMule also decodes the ed2k
server's packed rating (HIBYTE = % of clients that rated, LOBYTE = average) into a
0-5 aggregate (SearchFile.cpp:202-215), whereas aMule keeps only the quantized
0-3 self-rating.

5. Gap analysis

Piece aMule eMule Notes
Publish notes (STORENOTES) SharedFileList.cpp:1477, Search.cpp:817 parity
Parse NOTES response Search.cpp:1061 Search.cpp:1118 parity
Trigger NOTES lookup ❌ dead (SearchManager.cpp:321,468) CommentDialog(Lst).cpp missing
Route notes to CSearchFile ❌ no CSearchList::AddNotes Search.cpp:1202SearchList.cpp:769 missing
Notes API on base class ⚠️ m_kadNotes inherited but unused for search AbstractFile.h:111-136 needs wiring
Surface notes in UI (even downloads) m_kadNotes never read CommentDialogLst.cpp:149 missing
NOTES request needs file shared ⚠️ blocker (size from shared registry) handled via search context needs relaxing
Aggregate rating from server/Kad ⚠️ self-rating 0-3 only (SearchFile.cpp:82) ✅ 0-5 avg (SearchFile.cpp:202) fidelity gap

6. Proposed direction — core (aMule)

Framed as direction, not prescription:

  1. Add a NOTES-search trigger. Introduce the equivalent of eMule's
    PrepareLookup(CSearch::NOTES, hash) (revive the dead CSearch::NOTES branches).
    Relax the shared-file size lookup so it can take the size from the target
    CSearchFile/CPartFile rather than requiring the file to be shared.
  2. Route results to search hits. Add CSearchList::AddNotes(entry, hash) and
    call it from ProcessResultNotes alongside the existing shared/download matching,
    so notes attach to the CSearchFile (which already inherits m_kadNotes).
  3. Derive presence + aggregate. Add a CSearchFile::UpdateFileRatingCommentAvail
    equivalent that computes HasComment() and an averaged rating from m_kadNotes.
  4. Surface for downloads too. Merge m_kadNotes into aMule's comment view
    (extend GetRatingAndComments / the comment dialog to include Kad notes with an
    origin marker), matching eMule's merged list.

No new Kad wire format is required — the KADEMLIA2_*_NOTES_* opcodes already exist
and aMule already parses the response.

7. Proposed direction — downstream EC / REST / Web UI (aMule)

Since the remote/Web interface is the intended consumer, expose the above:

EC (amuled ↔ clients)

  • In CEC_SearchFile_Tag (src/ECSpecialCoreTags.cpp:365-386), add a has_comment
    flag and the aggregate rating once notes are attached.
  • Add an EC op to (i) trigger a Kad-notes fetch for a given hash and (ii)
    return the notes list, modelled on the existing per-source comments path
    (EC_TAG_PARTFILE_COMMENTS=0x0316, EC_TAG_KNOWNFILE_COMMENT=0x040E). Each note:
    filename, rating, comment (and origin = Kad).

REST (amuleapi)

  • POST /api/v0/search/results/{hash}/comments/fetch — kick off the Kad-notes
    lookup for that hash. Async: notes arrive later via the refresher (like search
    results do today).
  • GET /api/v0/search/results/{hash}/comments — return the collected notes:
    { "count": 2,
      "comments": [ { "origin": "kad", "username": null, "filename": "movie.mkv",
                      "rating": 4, "comment": "great quality" } ] }
    Reuse the shape defined by .claude/files/02-file-comments-ratings.md (add an
    origin field to distinguish kad vs ed2k).
  • Add has_comment (bool) and the aggregate rating to the search result object in
    WriteSearchObject (src/webapi/Api.cpp:3681-3702), decoded in ApplySearchFull
    (src/webapi/Refresher.cpp:1552-1591).

Web UI (src/webapi/static)

  • A "has comment" indicator + aggregate rating on each search row.
  • An on-demand "fetch comments" action per result that calls the fetch
    endpoint, then shows the returned notes in a panel (with the eD2K/Kad origin).

8. Open questions for discussion

  • On-demand vs automatic. eMule fetches notes only when the user asks (avoids
    flooding Kad with lookups). Automatic prefetch for every visible result could be
    abusive/slow. Recommend on-demand, mirroring eMule.
  • Spam / word filtering. eMule filters note text (GetCommentFilter,
    RefilterKadNotes, AbstractFile.cpp:411). Should aMule port this before showing
    arbitrary user-published text in the Web UI?
  • The shared-file requirement (Search.cpp:604) must be relaxed for
    search-result use — is there a reason it was scoped to shared files?
  • Aggregate rating fidelity. Worth also adopting eMule's packed-rating decode
    (0-5 average with % of raters) rather than the 0-3 self-rating? Related but
    separable from Kad notes.
  • Privacy. Publishing your own comment/rating to Kad exposes it network-wide;
    the Web UI should make writing a comment an explicit, opt-in action.
  • Availability caveat. Notes only exist if Kad nodes host them; many files will
    return nothing. The UI must distinguish "no comments" from "not fetched / Kad
    unavailable".

9. Effort / risk

  • Core: medium/high — touches Kademlia search lifecycle and search-list wiring,
    but no new wire protocol (the NOTES opcodes and parser already exist). The main
    work is reviving the trigger, relaxing the shared-file constraint, routing to
    CSearchFile, and surfacing m_kadNotes.
  • EC/REST/Web UI: additive and low-risk, following existing patterns (the search
    result serializer and the per-source comments design in .claude/files/02).
  • Independent of the four search issues in this folder (01-04); can be
    scoped/scheduled on its own.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions