// Mutates the in-memory fleet over time so the UI shows live activity: // speeds fluctuate, downloads progress, pieces fill in, ETA/ratio drift, // session totals accumulate, and the occasional torrent completes. import { db, constants } from './data.js'; const { KiB, MiB } = constants; function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); } function jitter(v, frac) { return v * (1 + (Math.random() * 2 - 1) * frac); } const DL_STATES = new Set(['downloading', 'forcedDL', 'metaDL']); const UP_STATES = new Set(['uploading', 'forcedUP']); export function tick() { const now = Date.now(); for (const t of db.torrents) { const isDL = DL_STATES.has(t.state); const isUP = UP_STATES.has(t.state) || isDL; // --- speeds --- if (isDL) { const ceiling = t.downLimit > 0 ? t.downLimit : 18 * MiB; t.dlspeed = clamp(Math.round(jitter(t.dlspeed || 500 * KiB, 0.4)), 20 * KiB, ceiling); // occasionally stall if (Math.random() < 0.03) { t.state = 'stalledDL'; t.dlspeed = 0; } } else if (t.state === 'stalledDL') { t.dlspeed = 0; if (Math.random() < 0.08) { t.state = 'downloading'; t.dlspeed = 800 * KiB; } } else { t.dlspeed = 0; } if (isUP) { const ceiling = t.upLimit > 0 ? t.upLimit : 6 * MiB; t.upspeed = clamp(Math.round(jitter(t.upspeed || 100 * KiB, 0.5)), 0, ceiling); } else { t.upspeed = 0; } // --- progress --- if (isDL && t.progress < 1 && t.dlspeed > 0) { const gained = t.dlspeed; // ~1s of bytes t.downloaded += gained; t.downloadedSession += gained; t.progress = clamp(t.progress + gained / t.size, 0, 1); // fill some pieces proportionally const targetDone = Math.floor(t.pieceCount * t.progress); let done = 0; for (const p of t.pieces) if (p === 2) done++; let toFill = targetDone - done; for (let i = 0; i < t.pieces.length && toFill > 0; i++) { if (t.pieces[i] !== 2) { if (t.pieces[i] === 1 || Math.random() < 0.5) { t.pieces[i] = 2; toFill--; } else t.pieces[i] = 1; } } if (t.state === 'metaDL' && t.progress > 0.01) t.state = 'downloading'; // completion if (t.progress >= 1) { t.progress = 1; t.state = 'uploading'; t.completionOn = now; t.dlspeed = 0; for (let i = 0; i < t.pieces.length; i++) t.pieces[i] = 2; } } // --- uploaded / ratio --- if (t.upspeed > 0) { t.uploaded += t.upspeed; t.uploadedSession += t.upspeed; } t.ratio = t.downloaded > 0 ? t.uploaded / t.downloaded : (t.uploaded > 0 ? 9999 : 0); // --- eta --- t.eta = isDL && t.dlspeed > 0 ? Math.floor((t.size - t.downloaded) / t.dlspeed) : 8640000; // --- swarm drift --- if (!t.state.startsWith('paused')) { t.seeds = clamp(t.seeds + (Math.random() < 0.5 ? -1 : 1) * (Math.random() < 0.3 ? 1 : 0), 0, t.seedsTotal); t.peers = clamp(t.peers + (Math.random() < 0.5 ? -1 : 1) * (Math.random() < 0.3 ? 1 : 0), 0, t.peersTotal); t.lastActivity = now; t.timeActive += 1; } // --- peer speeds drift --- for (const p of t.peersList) { if (p.dlspeed) p.dlspeed = clamp(Math.round(jitter(p.dlspeed, 0.5)), 0, 4 * MiB); if (p.upspeed) p.upspeed = clamp(Math.round(jitter(p.upspeed, 0.5)), 0, 800 * KiB); if (p.progress < 1) p.progress = clamp(p.progress + Math.random() * 0.01, 0, 1); } } } // Global session/server stats derived from the fleet. export function globalStats() { let dl = 0, up = 0, dlSession = 0, upSession = 0, totalDown = 0, totalUp = 0; let active = 0; for (const t of db.torrents) { dl += t.dlspeed; up += t.upspeed; dlSession += t.downloadedSession; upSession += t.uploadedSession; totalDown += t.downloaded; totalUp += t.uploaded; if (t.dlspeed > 0 || t.upspeed > 0) active++; } const p = db.preferences; return { dl_info_speed: dl, up_info_speed: up, dl_info_data: dlSession, up_info_data: upSession, dl_rate_limit: p.alt_speed_enabled ? p.alt_dl_limit : p.dl_limit, up_rate_limit: p.alt_speed_enabled ? p.alt_up_limit : p.up_limit, alt_speed_enabled: p.alt_speed_enabled, global_ratio: totalDown > 0 ? totalUp / totalDown : 0, dht_nodes: 312 + Math.floor(Math.random() * 40), connection_status: 'connected', listen_port: p.listen_port, free_space: 812 * 1024 * 1024 * 1024 + Math.floor(Math.random() * 1e9), active_torrents: active, total_torrents: db.torrents.length, queued_io_jobs: Math.floor(Math.random() * 4), read_cache_hits: (88 + Math.random() * 8).toFixed(1), total_buffer_size: 16 * MiB, average_time_queue: Math.floor(Math.random() * 12), }; }