From 85bac5994e69206b352dd375603e87ef43d9b2eb Mon Sep 17 00:00:00 2001 From: Gronod Date: Fri, 15 May 2026 23:46:51 +0100 Subject: [PATCH] feat: make background polling disablable with on-demand fallback - Set POLL_INTERVAL=0, off, false, or disabled to disable background polling - When disabled, data is fetched on-demand when a user opens the dashboard - On-demand results cached for 30s so other users benefit from fresh data - A user with a faster refresh rate keeps the cache warm for everyone - When polling is enabled, behaviour is unchanged (default 5s) --- server/index.js | 4 ++-- server/routes/dashboard.js | 10 +++++++++- server/utils/poller.js | 17 +++++++++++++---- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/server/index.js b/server/index.js index b285ce1..7231319 100644 --- a/server/index.js +++ b/server/index.js @@ -54,7 +54,7 @@ const radarrRoutes = require('./routes/radarr'); const embyRoutes = require('./routes/emby'); const dashboardRoutes = require('./routes/dashboard'); const authRoutes = require('./routes/auth'); -const { startPoller, POLL_INTERVAL } = require('./utils/poller'); +const { startPoller, POLL_INTERVAL, POLLING_ENABLED } = require('./utils/poller'); const app = express(); const PORT = process.env.PORT || 3001; @@ -80,7 +80,7 @@ app.listen(PORT, () => { console.log(` sofarr - Your Downloads Dashboard`); console.log(` Server running on port ${PORT}`); console.log(` Log level: ${process.env.LOG_LEVEL || 'info'}`); - console.log(` Poll interval: ${POLL_INTERVAL}ms`); + console.log(` Polling: ${POLLING_ENABLED ? POLL_INTERVAL + 'ms' : 'disabled (on-demand)'}`); console.log(`=================================`); startPoller(); }); diff --git a/server/routes/dashboard.js b/server/routes/dashboard.js index c1141ba..1e1d82c 100644 --- a/server/routes/dashboard.js +++ b/server/routes/dashboard.js @@ -3,6 +3,7 @@ const router = express.Router(); const { mapTorrentToDownload } = require('../utils/qbittorrent'); const cache = require('../utils/cache'); +const { pollAllServices, POLLING_ENABLED } = require('../utils/poller'); const EMBY_URL = process.env.EMBY_URL; const EMBY_API_KEY = process.env.EMBY_API_KEY; @@ -105,7 +106,14 @@ router.get('/user-downloads', async (req, res) => { const showAll = isAdmin && req.query.showAll === 'true'; console.log(`[Dashboard] Serving downloads for user: ${user.name} (${username}), isAdmin: ${isAdmin}, showAll: ${showAll}`); - // Read all data from poller cache + // When polling is disabled, fetch on-demand if cache has expired + // The fetched data is cached (30s TTL) so subsequent requests from any user reuse it + if (!POLLING_ENABLED && !cache.get('poll:sab-queue')) { + console.log(`[Dashboard] Cache expired and polling disabled, fetching on-demand...`); + await pollAllServices(); + } + + // Read all data from cache const sabQueueData = cache.get('poll:sab-queue') || { slots: [] }; const sabHistoryData = cache.get('poll:sab-history') || { slots: [] }; const sonarrTagsResults = cache.get('poll:sonarr-tags') || []; diff --git a/server/utils/poller.js b/server/utils/poller.js index 32c506e..205ad03 100644 --- a/server/utils/poller.js +++ b/server/utils/poller.js @@ -7,7 +7,11 @@ const { getRadarrInstances } = require('./config'); -const POLL_INTERVAL = parseInt(process.env.POLL_INTERVAL, 10) || 5000; +const rawPollInterval = (process.env.POLL_INTERVAL || '').toLowerCase(); +const POLL_INTERVAL = (rawPollInterval === 'off' || rawPollInterval === 'false' || rawPollInterval === 'disabled') + ? 0 + : (parseInt(process.env.POLL_INTERVAL, 10) || 5000); +const POLLING_ENABLED = POLL_INTERVAL > 0; let polling = false; @@ -125,8 +129,9 @@ async function pollAllServices() { }) ]); - // Aggregate and store in cache (TTL slightly longer than poll interval to avoid gaps) - const cacheTTL = POLL_INTERVAL * 3; + // When polling is active, TTL is 3x interval to avoid gaps between polls + // When polling is disabled (on-demand), use 30s so data refreshes on next request after expiry + const cacheTTL = POLLING_ENABLED ? POLL_INTERVAL * 3 : 30000; // SABnzbd const firstSabQueue = sabQueues[0] && sabQueues[0].data && sabQueues[0].data.queue; @@ -182,6 +187,10 @@ async function pollAllServices() { let intervalHandle = null; function startPoller() { + if (!POLLING_ENABLED) { + console.log(`[Poller] Background polling disabled (POLL_INTERVAL=${process.env.POLL_INTERVAL || 'not set'}). Data will be fetched on-demand.`); + return; + } console.log(`[Poller] Starting background poller (interval: ${POLL_INTERVAL}ms)`); // Run immediately, then on interval pollAllServices(); @@ -196,4 +205,4 @@ function stopPoller() { } } -module.exports = { startPoller, stopPoller, POLL_INTERVAL }; +module.exports = { startPoller, stopPoller, pollAllServices, POLL_INTERVAL, POLLING_ENABLED };