Skip to content

Improve rutracker_check updates for RuTracker and NNMClub#3048

Merged
xirvik merged 2 commits into
Novik:masterfrom
IvanShift:ivanshift/docker-rutorrent-overrides-port
Jul 23, 2026
Merged

Improve rutracker_check updates for RuTracker and NNMClub#3048
xirvik merged 2 commits into
Novik:masterfrom
IvanShift:ivanshift/docker-rutorrent-overrides-port

Conversation

@IvanShift

Copy link
Copy Markdown
Contributor

Rework the rutracker_check update flow so it can handle current tracker behavior without treating reachable torrents as deleted.

For NNMClub, use tracker scrape as the fast path with a real passkey from the current torrent or another session torrent, then fall back to unauthenticated guest torrent downloads when the scrape does not find the current hash. Patch downloaded guest torrents with the real passkey before replacing the existing torrent, which avoids the Cloudflare-protected login flow and prevents false STE_DELETED results.

For RuTracker, keep the API hash check but fall back to direct torrent download and absorbed-topic detection when the API reports a missing/deleted topic or the download endpoint returns an HTML error page instead of a .torrent file.

Harden shared replacement handling by matching trackers via comment or announce URL, preserving existing start/label/throttle/ratio state, skipping state updates for hashes that no longer exist, and cleaning up obsolete files after a successful replacement when the new torrent file list differs from the old one.

@xirvik xirvik closed this Jul 10, 2026
@xirvik xirvik reopened this Jul 10, 2026
@xirvik xirvik closed this Jul 16, 2026
@xirvik xirvik reopened this Jul 16, 2026
@xirvik

xirvik commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

@IvanShift can you rebase? And also tell me how to test?

IvanShift and others added 2 commits July 19, 2026 14:43
Rework the rutracker_check update flow so it can handle current tracker behavior without treating reachable torrents as deleted.

For NNMClub, use tracker scrape as the fast path with a real passkey from the current torrent or another session torrent, then fall back to unauthenticated guest torrent downloads when the scrape does not find the current hash. Patch downloaded guest torrents with the real passkey before replacing the existing torrent, which avoids the Cloudflare-protected login flow and prevents false STE_DELETED results.

For RuTracker, keep the API hash check but fall back to direct torrent download and absorbed-topic detection when the API reports a missing/deleted topic or the download endpoint returns an HTML error page instead of a .torrent file.

Harden shared replacement handling by matching trackers via comment or announce URL, preserving existing start/label/throttle/ratio state, skipping state updates for hashes that no longer exist, and cleaning up obsolete files after a successful replacement when the new torrent file list differs from the old one.
Replace the transaction's poll/verify machinery with a single ownership
marker and one bounded wait for rTorrent's deferred load_raw; every other
XMLRPC step is synchronous and checked once.

Recover from interrupted replacements: adopt stopped marker-carrying
staged copies left by crashed runs, restore the old torrent on every
pre-commit failure (including unknowable staged status), never treat a
live marked torrent as disposable, and finish cleanup/marker-clear even
when post-commit activation cannot be confirmed.

Keep obsolete-file cleanup from deleting data the new torrent still
references on case-insensitive or normalizing filesystems (inode guard).

Restore the STE_DELETED contract for legacy handlers, log curl transport
failures on the https path, and replace the recursive scrape bencode
decoder with a bounded substring probe.

Consolidate the PHP test suites onto a shared TestLib harness; the XMLRPC
double now records command parameters so hash-probe targets are pinned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@IvanShift
IvanShift force-pushed the ivanshift/docker-rutorrent-overrides-port branch from 3bff931 to 7b5d2d0 Compare July 19, 2026 18:55
@IvanShift

IvanShift commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

How to test

Automated — no rTorrent or network required:

php tests/plugins/rutracker_check/run.php

45 tests, 0 failures (CheckerTest 28, RuTracker 6, NNMClub 8, Snoopy 3), verified on PHP 8.1. They cover the replacement transaction and its rollback/recovery paths, NNMClub scrape + guest-download, RuTracker absorbed-topic detection, and the Snoopy HTTP-method forwarding.

Also green locally: php -l on the changed files, and git diff --check upstream/master...HEAD. The existing Jest suite is unaffected (7 suites, 43 tests).

Integration testing is still needed against a disposable ruTorrent/rTorrent instance, because the plugin talks to live trackers and session torrents and performs real torrent replacement. Do not use irreplaceable data: a successful update may delete files that existed only in the old torrent.

Prerequisites:

  • Enable the rutracker_check plugin.
  • For the RuTracker handler: enable loginmgr and configure valid RuTracker credentials (needed for dl.php). NNMClub is checked in guest mode and needs no login.
  • Use a writable rTorrent session directory.
  • Allow HTTPS access to RuTracker/NNMClub and HTTP access to the NNMClub tracker hosts on port 2710.
  • Prepare both current and outdated torrents for topics whose torrent file has changed.
  • For NNMClub, keep at least one session torrent carrying the account's real 32-character passkey.
  • Diagnostics: set $rutrackerCheckDebug = true in plugins/rutracker_check/conf.php; messages are then written to the configured ruTorrent log. Redact passkeys before sharing the log.
  • Trigger checks with the “Check for Update” context-menu action.
Detailed manual test matrix (NNMClub / RuTracker / replacement)

NNMClub

  1. Check an up-to-date torrent whose announce carries a real passkey. Expected: “No update required”; the info hash and the session .torrent remain unchanged.
  2. For a torrent with a reusable uk credential on a legacy host, make its primary scrape host unreachable. Expected: the plugin falls back to the current official endpoint (bt.searchtor.to) and completes the scrape.
  3. Make the scrape unavailable entirely. Expected: the guest topic/download fallback is used; a torrent that is still current is not reported as deleted.
  4. Check a torrent whose own announce has only a dummy/guest passkey while a different session torrent holds the account's real passkey. Expected: the donor passkey is discovered and used for the scrape (and, if a replacement is required, injected into the downloaded torrent); the existing session .torrent files are left byte-for-byte unchanged. Restart rTorrent and confirm every torrent resumes normally.
  5. Check an outdated torrent whose topic now has a new torrent. Expected: the guest torrent is downloaded, patched with the real passkey, loaded under the new hash, and the old hash is removed.
  6. Repeat step 5 with no real passkey anywhere in the session. Expected: replacement is refused and the old torrent stays intact.
  7. Return a Cloudflare challenge, an HTML error, or a malformed torrent. Expected: a tracker/rTorrent error; no torrent and no data are removed.

RuTracker

  1. API hash equals the current hash. Expected: “No update required”.
  2. API hash differs and dl.php returns a valid torrent. Expected: the new hash replaces the old hash.
  3. Test an absorbed/merged topic, including one whose merge notice appears on the final pagination page. Expected: the replacement topic is found and downloaded.
  4. Make dl.php return an HTML login/error page. Expected: a tracker error rather than “Probably deleted”; the old torrent stays intact.
  5. Test a genuinely removed topic with no replacement link. Expected: “Probably deleted”.

Replacement / regression

  1. Before replacement, set a directory, label, throttle and ratio group; repeat with both active and stopped torrents. Expected: all settings and the running/stopped state are preserved.
  2. Update a multi-file torrent where one file was renamed or removed. Expected: old-only files and the empty directories left behind are removed, while shared and unrelated files stay untouched.
  3. Force a failure before the new torrent is loaded, and again before the old one is erased. Expected: cleanup does not run and the old data stays intact.
  4. Poll the UI or start another check while a replacement is in progress. Expected: no stuck “In progress” state and no noisy stale-hash XMLRPC errors.
  5. Smoke-test an unchanged handler such as Kinozal or Tapochek. Expected: existing behaviour is unchanged.
  • Diagnostics: set $rutrackerCheckDebug = true in plugins/rutracker_check/conf.php. Messages are then appended to ruTorrent's shared log file ($log_file in conf/config.php), which defaults to /tmp/errors.log and can be overridden via the RU_LOG_FILE environment variable. Each line is prefixed with rutracker_check:, so grep rutracker_check: /tmp/errors.log isolates them; if $log_file is empty, logging is disabled. Redact passkeys before sharing the log.

P.S. Reproducing these integration scenarios against live trackers is not trivial: the hard part is finding real topics that actually exercise the interesting cases — a topic re-uploaded on a regular basis (RuTracker and NNMClub), a topic absorbed/merged (only for RuTracker) into another release, and an update that renames/removes some of the previously downloaded files (any trackers). This coverage can come from a single topic that happens to hit several cases at once, or from several topics that each cover one or two, so lining it all up on real trackers takes some time.

@xirvik

xirvik commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

I tested the best I could with rutracker. Didn't test NNMClub . But for the future:

  • No AI slop when interacting with humans (i.e. me). It's fine to use it for the commit message, but to answer a direct question please type an answer yourself; I don't want to spend time parsing a long text, and definitely not do all those tests - I want to test as an user, make sure it works as claimed. And if I need to create an account somewhere, be specific on how, including if needed sending me an invite.

Merging because I was able to test with rutracker and it seems to work.

@xirvik
xirvik merged commit d056acd into Novik:master Jul 23, 2026
4 checks passed
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.

2 participants