• src/sbbs3/atcodes.cpp file.cpp filedat.c getstats.c getstats.h js_file

    From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Mon Aug 17 02:07:18 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/a464bc3efd939dc2b6741214
    Modified Files:
    src/sbbs3/atcodes.cpp file.cpp filedat.c getstats.c getstats.h js_filebase.cpp js_system.cpp postmsg.cpp src/sbbs3/scfg/scfgsys.c src/sbbs3/scfgdefs.h scfglib1.c scfgsave.c
    Log Message:
    Cache the system-wide message and file totals

    Neither total can be maintained as a counter: messages and files enter and leave the bases through too many independent paths (JS, smbutil, the
    terminal server, FTP, mail, services, web) for an incremented count to
    stay true. They can only be obtained by enumerating every base - one file operation per sub-board or directory. That is cheap locally, but on a network-mounted data directory each one is a separate round-trip that
    cannot be pipelined: with 144 sub-boards and 1,404 file directories
    reached over SMB, a single pass costs roughly nine seconds, which is essentially the entire render time of the webv4 front page.

    Add total_msgs() and total_files(), which share one enumeration
    process-wide for the duration of a new totals_interval setting (default
    10 minutes, 0 to count afresh every time). The stats-file cache added in 286b4c4cd0 (nurse-3-film, 2026-01-04) deliberately does not cover these: getstats() reads a file, and stats_t is its on-disk image, so folding an enumeration in there would make every existing getstats_cached() caller
    pay for a walk it does not want.

    The @TMSG@ and @TFILE@ at-codes and the system.stats.total_messages and .total_files properties each ran their own copy of the loop; both now go through the cache. The value is carried as uint64_t rather than ulong,
    which would truncate these totals on a 32-bit build.

    Bases changed by this process discard the affected total immediately, so
    a poster sees their own contribution counted rather than waiting out the interval. For messages the hook belongs in signal_sub_sem(), which is
    already the "this sub-board changed" notification and so covers posting,
    QWK and REP import, and message moves in one place; postpoll() needs its
    own, as it does not signal but polls are counted. For files the hooks are
    in addfile()/removefile(), sbbs_t::removefile() and the JS FileBase
    mutators.

    A change made by another process is not noticed until the interval
    expires, and the extra recipient headers written by js_msgbase are not
    hooked. That is a staleness window rather than an inaccuracy:
    invalidation only ever discards a cached value, it never adjusts one, so
    a path these hooks miss costs currency, not correctness. The cache lives
    in process memory only; nothing is persisted.

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net