// Mock data store for the torrent UI. // Generates a realistic "fleet" of torrents with full per-torrent detail // (trackers, peers, files, piece map) plus categories, tags, RSS and search stubs. const KiB = 1024; const MiB = 1024 * KiB; const GiB = 1024 * MiB; // ---- deterministic-ish PRNG so reloads look stable but varied ---- let _seed = 1337; function rng() { _seed = (_seed * 1103515245 + 12345) & 0x7fffffff; return _seed / 0x7fffffff; } function pick(arr) { return arr[Math.floor(rng() * arr.length)]; } function between(min, max) { return min + rng() * (max - min); } function intBetween(min, max) { return Math.floor(between(min, max + 1)); } function chance(p) { return rng() < p; } function fakeHash() { const hex = '0123456789abcdef'; let s = ''; for (let i = 0; i < 40; i++) s += hex[Math.floor(rng() * 16)]; return s; } const COUNTRIES = ['US', 'DE', 'NL', 'FR', 'GB', 'SE', 'CA', 'JP', 'AU', 'RU', 'BR', 'CN', 'IN', 'PL', 'UA', 'CH']; const CLIENTS = [ 'qBittorrent 4.6.5', 'qBittorrent 5.0.0', 'Transmission 4.0.5', 'Deluge 2.1.1', 'libtorrent 2.0.10', 'Vuze 5.7.7', 'rTorrent 0.9.8', 'BiglyBT 3.6.0', 'µTorrent 3.6.0', 'WebTorrent 2.1.0', 'Tixati 3.27', ]; const PEER_FLAGS = ['D', 'U', 'O', 'S', 'I', 'E', 'X', 'H', 'P', 'K', '?']; const CATEGORIES = [ { name: 'Linux ISOs', savePath: '/data/iso' }, { name: 'Movies', savePath: '/data/media/movies' }, { name: 'TV', savePath: '/data/media/tv' }, { name: 'Music', savePath: '/data/media/music' }, { name: 'Books', savePath: '/data/books' }, { name: 'Datasets', savePath: '/data/datasets' }, { name: 'Games', savePath: '/data/games' }, { name: '', savePath: '/data/downloads' }, // uncategorized ]; const TAGS = ['archive', 'public', 'private', 'seed-forever', 'hit-and-run', 'verified', '4k', 'remux', 'incomplete', 'priority']; const TRACKER_HOSTS = [ 'tracker.opentrackr.org:1337', 'open.demonii.com:1337', 'tracker.torrent.eu.org:451', 'exodus.desync.com:6969', 'tracker.openbittorrent.com:6969', 'private.tracker.lan:2710', 'tracker.dler.org:6969', 'open.stealth.si:80', '** [DHT] **', '** [PeX] **', '** [LSD] **', ]; const NAME_TEMPLATES = [ 'debian-12.5.0-amd64-netinst.iso', 'ubuntu-24.04-desktop-amd64.iso', 'archlinux-2024.06.01-x86_64.iso', 'Fedora-Workstation-Live-x86_64-40.iso', 'NixOS-24.05-x86_64-linux.iso', 'The.Expanse.S01.2160p.UHD.BluRay.REMUX.HDR.DV', 'Cosmos.A.Spacetime.Odyssey.S01.1080p.BluRay', 'Blender.Open.Movies.Collection.2006-2023', 'Big.Buck.Bunny.4K.60fps.HDR', 'Sintel.2010.2160p.BluRay.x265', 'MIT.OCW.6.006.Introduction.to.Algorithms.2020', 'Stanford.CS231n.2017.Lectures', 'Wikipedia.en.all.maxi.2024-05.zim', 'OpenStreetMap.planet-240603.osm.pbf', 'Common.Voice.Corpus.17.0.en', 'ImageNet.ILSVRC2012.tar', 'GPL.Source.Mirror.linux-6.9.4', 'Project.Gutenberg.2024.Snapshot', 'FreeBSD-14.0-RELEASE-amd64-dvd1.iso', 'TempleOS.Distro.5.03', 'Public.Domain.Jazz.Collection.FLAC', 'NASA.Apollo.Archive.4K.Scans', 'OpenStax.Textbook.Bundle.2024', 'Rocky.Linux.9.4.x86_64.dvd.iso', ]; const STATES = { downloading: 'downloading', stalledDL: 'stalledDL', uploading: 'uploading', stalledUP: 'stalledUP', pausedDL: 'pausedDL', pausedUP: 'pausedUP', checkingDL: 'checkingDL', queuedDL: 'queuedDL', forcedDL: 'forcedDL', forcedUP: 'forcedUP', metaDL: 'metaDL', error: 'error', missingFiles: 'missingFiles', moving: 'moving', }; function makeFiles(name, totalSize) { // multi-file torrents get a tree; single-file otherwise. const isMulti = chance(0.55); if (!isMulti) { return [{ name, size: totalSize, progress: 1, priority: 1, availability: 1, }]; } const folder = name.replace(/\.[a-z0-9]+$/i, ''); const count = intBetween(2, 9); const files = []; let remaining = totalSize; const subdirs = ['', '', 'extras/', 'subs/', 'sample/']; for (let i = 0; i < count; i++) { const portion = i === count - 1 ? remaining : Math.floor(remaining * between(0.1, 0.5)); remaining -= portion; const ext = pick(['.mkv', '.mp4', '.flac', '.iso', '.pdf', '.tar', '.zim', '.nfo', '.srt']); const prio = pick([0, 1, 1, 1, 6, 7]); // 0=do not download,1=normal,6=high,7=max files.push({ name: `${folder}/${pick(subdirs)}part${i + 1}${ext}`, size: Math.max(portion, MiB), progress: prio === 0 ? 0 : between(0.2, 1), priority: prio, availability: between(0.8, 2.5), }); } return files; } function makePieces(count, progress) { // 0 = missing, 1 = downloading, 2 = done const arr = new Array(count).fill(0); const done = Math.floor(count * progress); // make completed pieces somewhat scattered to look realistic let filled = 0; for (let i = 0; i < count && filled < done; i++) { if (chance(0.85)) { arr[i] = 2; filled++; } } // remaining done pieces fill from the front for (let i = 0; i < count && filled < done; i++) { if (arr[i] === 0) { arr[i] = 2; filled++; } } // a few in-flight if (progress < 1) { for (let i = 0; i < count; i++) { if (arr[i] === 0 && chance(0.02)) arr[i] = 1; } } return arr; } function makeTrackers(seeds, peers) { const n = intBetween(2, 4); const hosts = [...TRACKER_HOSTS]; const trackers = []; // always include DHT/PeX/LSD pseudo-trackers for (const pseudo of ['** [DHT] **', '** [PeX] **', '** [LSD] **']) { trackers.push({ url: pseudo, tier: -1, status: 'working', seeds: pseudo.includes('DHT') ? intBetween(0, seeds) : -1, peers: pseudo.includes('DHT') ? intBetween(0, peers) : -1, leeches: -1, downloaded: -1, message: '', }); } for (let i = 0; i < n; i++) { const url = `udp://${pick(hosts)}/announce`; const working = chance(0.78); trackers.push({ url, tier: i, status: working ? 'working' : pick(['not contacted', 'updating', 'error']), seeds: working ? intBetween(0, seeds + 50) : 0, peers: working ? intBetween(0, peers + 30) : 0, leeches: working ? intBetween(0, peers + 30) : 0, downloaded: working ? intBetween(100, 50000) : 0, message: working ? '' : pick(['Connection timed out', 'Host not found', 'Not working', 'unregistered torrent']), }); } return trackers; } function makePeers(count, dlActive) { const peers = []; const realCount = Math.min(count, intBetween(0, 40)); for (let i = 0; i < realCount; i++) { const flags = []; if (dlActive && chance(0.5)) flags.push('D'); if (chance(0.4)) flags.push('U'); if (chance(0.3)) flags.push('O'); // optimistic unchoke if (chance(0.2)) flags.push('I'); // incoming if (chance(0.5)) flags.push('E'); // encrypted if (chance(0.1)) flags.push('X'); // PEX if (chance(0.1)) flags.push('H'); // DHT const prog = between(0, 1); peers.push({ ip: `${intBetween(1, 254)}.${intBetween(0, 254)}.${intBetween(0, 254)}.${intBetween(1, 254)}`, port: intBetween(1024, 65535), country: pick(COUNTRIES), client: pick(CLIENTS), flags: flags.join(' '), progress: prog, dlspeed: flags.includes('D') ? intBetween(10 * KiB, 4 * MiB) : 0, upspeed: flags.includes('U') ? intBetween(1 * KiB, 800 * KiB) : 0, downloaded: intBetween(0, 500 * MiB), uploaded: intBetween(0, 500 * MiB), relevance: prog, connection: pick(['µTP', 'BT', 'BT', 'WEB']), }); } return peers; } function makeTorrent(name, idx) { const size = Math.floor(between(150 * MiB, 60 * GiB)); const pieceSizeOptions = [256 * KiB, 512 * KiB, 1 * MiB, 2 * MiB, 4 * MiB, 8 * MiB, 16 * MiB]; const pieceSize = pick(pieceSizeOptions); const pieceCount = Math.max(8, Math.min(2400, Math.ceil(size / pieceSize))); // pick a plausible state const stateRoll = rng(); let state, progress; if (stateRoll < 0.30) { state = STATES.downloading; progress = between(0.05, 0.95); } else if (stateRoll < 0.40) { state = STATES.stalledDL; progress = between(0.0, 0.6); } else if (stateRoll < 0.62) { state = STATES.uploading; progress = 1; } else if (stateRoll < 0.74) { state = STATES.stalledUP; progress = 1; } else if (stateRoll < 0.82) { state = STATES.pausedUP; progress = 1; } else if (stateRoll < 0.87) { state = STATES.pausedDL; progress = between(0.1, 0.8); } else if (stateRoll < 0.90) { state = STATES.queuedDL; progress = between(0, 0.3); } else if (stateRoll < 0.93) { state = STATES.checkingDL; progress = between(0.3, 0.99); } else if (stateRoll < 0.95) { state = STATES.forcedUP; progress = 1; } else if (stateRoll < 0.97) { state = STATES.metaDL; progress = 0; } else if (stateRoll < 0.99) { state = STATES.error; progress = between(0, 0.9); } else { state = STATES.missingFiles; progress = between(0.5, 1); } const isDL = ['downloading', 'forcedDL', 'metaDL'].includes(state); const isUP = ['uploading', 'forcedUP'].includes(state); const seedsTotal = intBetween(0, 800); const peersTotal = intBetween(0, 600); const seeds = Math.min(seedsTotal, intBetween(0, 50)); const peers = Math.min(peersTotal, intBetween(0, 40)); const dlspeed = isDL ? intBetween(50 * KiB, 18 * MiB) : 0; const upspeed = (isUP || isDL) ? intBetween(0, 6 * MiB) : 0; const downloaded = Math.floor(size * progress + intBetween(0, 200 * MiB)); const ratio = between(0, 8.5); const uploaded = Math.floor(downloaded * ratio); const now = Date.now(); const addedOn = now - intBetween(60, 60 * 60 * 24 * 90) * 1000; const completionOn = progress >= 1 ? addedOn + intBetween(60, 60 * 60 * 24) * 1000 : -1; const cat = pick(CATEGORIES); const tagSet = new Set(); const tagCount = intBetween(0, 3); for (let i = 0; i < tagCount; i++) tagSet.add(pick(TAGS)); const eta = isDL && dlspeed > 0 ? Math.floor((size - downloaded) / dlspeed) : 8640000; // ∞ sentinel (100 days) const files = makeFiles(name, size); const isPrivate = chance(0.35); return { hash: fakeHash(), name, size, progress: Math.min(1, progress), dlspeed, upspeed, state, eta, seeds, seedsTotal, peers, peersTotal, ratio, ratioLimit: chance(0.3) ? between(1, 4) : -1, category: cat.name, tags: [...tagSet], savePath: cat.savePath, contentPath: `${cat.savePath}/${name}`, addedOn, completionOn, lastActivity: now - intBetween(0, 60 * 60 * 12) * 1000, seenComplete: completionOn, downloaded, uploaded, downloadedSession: Math.floor(downloaded * between(0.01, 0.3)), uploadedSession: Math.floor(uploaded * between(0.01, 0.3)), availability: state.includes('paused') ? 0 : between(0.5, 4), priority: ['queuedDL', 'queuedUP'].includes(state) ? intBetween(1, 12) : 0, seqDl: chance(0.15), superSeeding: progress >= 1 && chance(0.1), autoTMM: chance(0.5), forceStart: state.startsWith('forced'), pieceSize, pieceCount, pieces: makePieces(pieceCount, Math.min(1, progress)), downLimit: chance(0.2) ? intBetween(50 * KiB, 5 * MiB) : 0, upLimit: chance(0.2) ? intBetween(20 * KiB, 2 * MiB) : 0, timeActive: intBetween(60, 60 * 60 * 24 * 60), comment: chance(0.4) ? pick(['Verified release', 'Please seed!', 'Official mirror', 'See README for checksums']) : '', createdBy: pick(['mktorrent 1.1', 'qBittorrent v5.0.0', 'Transmission/4.0.5', 'libtorrent']), creationDate: addedOn - intBetween(60 * 60 * 24, 60 * 60 * 24 * 365) * 1000, private: isPrivate, magnetUri: `magnet:?xt=urn:btih:${''}`, files, trackers: makeTrackers(seeds, peers), peersList: makePeers(peers, isDL), }; } function buildFleet() { _seed = 1337; const torrents = NAME_TEMPLATES.map((n, i) => makeTorrent(n, i)); for (const t of torrents) t.magnetUri = `magnet:?xt=urn:btih:${t.hash}&dn=${encodeURIComponent(t.name)}`; return torrents; } const torrents = buildFleet(); const rssFeeds = [ { uid: 'feed-1', name: 'Linux ISO Tracker', url: 'https://tracker.example.org/rss/linux', lastUpdate: Date.now() - 8 * 60 * 1000, articles: [ { title: 'debian-12.6.0-amd64-netinst.iso', date: Date.now() - 60 * 60 * 1000, size: 660 * MiB, isRead: false }, { title: 'ubuntu-24.04.1-desktop-amd64.iso', date: Date.now() - 5 * 60 * 60 * 1000, size: 5.9 * GiB, isRead: true }, { title: 'archlinux-2024.07.01-x86_64.iso', date: Date.now() - 26 * 60 * 60 * 1000, size: 1.1 * GiB, isRead: true }, ], }, { uid: 'feed-2', name: 'Public Domain Media', url: 'https://media.example.org/rss', lastUpdate: Date.now() - 22 * 60 * 1000, articles: [ { title: 'Sintel.2010.2160p.BluRay.x265', date: Date.now() - 2 * 60 * 60 * 1000, size: 4.2 * GiB, isRead: false }, { title: 'Big.Buck.Bunny.4K.60fps.HDR', date: Date.now() - 9 * 60 * 60 * 1000, size: 2.8 * GiB, isRead: false }, ], }, ]; const rssRules = [ { name: 'New Debian stable', enabled: true, mustContain: 'debian amd64 netinst', mustNotContain: 'rc beta alpha', useRegex: false, episodeFilter: '', affectedFeeds: ['feed-1'], assignedCategory: 'Linux ISOs', savePath: '/data/iso', addPaused: false, lastMatch: Date.now() - 60 * 60 * 1000, }, { name: '4K Public Domain', enabled: true, mustContain: '2160p', mustNotContain: 'cam ts', useRegex: false, episodeFilter: '', affectedFeeds: ['feed-2'], assignedCategory: 'Movies', savePath: '/data/media/movies', addPaused: true, lastMatch: Date.now() - 2 * 60 * 60 * 1000, }, { name: 'Ubuntu LTS only', enabled: false, mustContain: 'ubuntu.*desktop.*amd64', mustNotContain: 'daily beta', useRegex: true, episodeFilter: '', affectedFeeds: ['feed-1'], assignedCategory: 'Linux ISOs', savePath: '/data/iso', addPaused: false, lastMatch: -1, }, ]; const searchPlugins = [ { name: 'LinuxTracker', enabled: true, url: 'https://linuxtracker.org' }, { name: 'PublicMediaDB', enabled: true, url: 'https://media.example.org' }, { name: 'AcademicTorrents', enabled: true, url: 'https://academictorrents.com' }, { name: 'LegacyIndexer', enabled: false, url: 'https://legacy.example.net' }, ]; function runSearch(query) { const q = (query || '').toLowerCase().trim(); const pool = [ ...NAME_TEMPLATES, 'OpenWRT-23.05.3-x86-64-generic.img', 'KDE.neon.User.Edition.2024.iso', 'Manjaro.KDE.24.0.iso', 'Public.Domain.Films.1920s.Collection', 'LibreOffice.24.2.SDK.docs', ]; return pool .filter((n) => !q || n.toLowerCase().includes(q)) .map((n) => ({ name: n, size: Math.floor(between(100 * MiB, 50 * GiB)), seeds: intBetween(0, 1200), leeches: intBetween(0, 600), engine: pick(searchPlugins.filter((p) => p.enabled).map((p) => p.name)), pubDate: Date.now() - intBetween(60, 60 * 60 * 24 * 400) * 1000, descrLink: 'https://example.org/details', })) .sort((a, b) => b.seeds - a.seeds); } export const db = { torrents, categories: CATEGORIES, tags: TAGS, rssFeeds, rssRules, searchPlugins, runSearch, // server preferences (subset, advanced) preferences: { dl_limit: 0, up_limit: 0, alt_dl_limit: 1 * MiB, alt_up_limit: 256 * KiB, alt_speed_enabled: false, max_connec: 500, max_connec_per_torrent: 100, max_uploads: 20, max_active_downloads: 5, max_active_uploads: 10, max_active_torrents: 12, dht: true, pex: true, lsd: true, encryption: 1, // 0=prefer,1=force on,2=force off utp: true, listen_port: 6881, upnp: true, queueing_enabled: true, save_path: '/data/downloads', scan_interval: 15, }, }; export const constants = { KiB, MiB, GiB, STATES };