"use strict"; class trweb { #servers = {}; #knownTorrents = {}; #needsSort = false; #loadcb = function(key) {return localStorage.getItem(`trweb.${key}`)}; #savecb = function(key, value) {localStorage.setItem(`trweb.${key}`, value)}; #logger = function(msg) {}; #timer; #container; #guiHeader; #torrentListView; #guiFooter; constructor (container, loadcb, savecb, logger) { this.#container = container; if (loadcb != null) { this.#loadcb = loadcb; this.#savecb = savecb; } if (logger != null) { this.#logger = logger; } if (document.readyState === 'complete' || document.readyState === 'interactive') { this.#logger('Document already loaded, jumpstarting'); this.#handler({'type': 'DOMContentLoaded'}); } else { this.#logger('Waiting for document to load'); document.addEventListener('DOMContentLoaded', this.#handler.bind(this)); } } #registerServer(name, initdata) { let torrentCallback = function(hash, torrentInfo) { let mergedTorrentInfo = this.#knownTorrents[hash]; if (mergedTorrentInfo == null) { mergedTorrentInfo = this.#createTorrentEntry(hash); } mergedTorrentInfo.servers[name] = torrentInfo; mergedTorrentInfo.name = torrentInfo.name; this.#updateTorrentDisplay(mergedTorrentInfo); }; let newserver = new trserver(initdata, function(msg) {this.#logger(`[Server: ${name}]: ${msg}`);}.bind(this), torrentCallback.bind(this)); this.#servers[name] = newserver; newserver.refreshTorrentList(); } #loadServers() { let serverjson = this.#loadcb('servers'); this.#servers = {}; if (serverjson == null) { this.#logger('No saved server data found'); } else { let serverdata = JSON.parse(serverjson); for (const [key, value] of Object.entries(serverdata)) { this.#registerServer(key, value); } this.#logger(`Loaded ${Object.keys(this.#servers).length} servers`); } } #saveServers() { let serverdata = {}; for (const [key, value] of Object.entries(this.#servers)) { serverdata[key] = value.getInstanceData(); } this.#logger(`Saving ${Object.keys(serverdata).length} servers`); this.#savecb('servers', JSON.stringify(serverdata)); } #createTorrentEntry(hash) { let entry = { 'element': document.createElement('div'), 'element_name': document.createElement('div'), 'element_server': {}, 'hash': hash, 'name': '## NAME NOT SET ##', 'servers': {} }; this.#knownTorrents[hash] = entry; // set up DOM structure entry.element.torrent = entry; entry.element.classList.add('trweb_torrentlistentry', 'trweb_hide_buttons', 'row'); entry.element.setAttributeNS('trweb', 'torrentlistfield', 'entry'); entry.element.addEventListener('click', this.#handler.bind(this)); entry.element_name.classList.add('trweb_torrentlistname', 'col'); entry.element_name.setAttributeNS('trweb', 'fieldid', 'displayname'); entry.element.appendChild(entry.element_name); for (const srv of Object.keys(this.#servers)) { this.#createServerEntryForTorrent(hash, srv); } this.#torrentListView.appendChild(entry.element); this.#updateTorrentDisplay(entry); return entry; } #createServerEntryForTorrent(hash, srv) { this.#needsSort = true; let entry = this.#knownTorrents[hash]; let element_server = document.createElement('div'); element_server.classList.add('trweb_torrentlistserver', 'col-2', 'container'); element_server.setAttributeNS('trweb', 'torrentlistfield', 'server'); element_server.setAttributeNS('trweb', 'server', srv); entry.element_server[srv] = element_server; entry.element.appendChild(entry.element_server[srv]); // statusbar let element_statusbar = document.createElement('div'); element_statusbar.classList.add('trweb_torrentliststatusbar'); element_server.element_statusbar = element_statusbar; element_server.appendChild(element_statusbar); // row container let element_serverrow = document.createElement('div'); element_serverrow.classList.add('trweb_torrentlistserverrow', 'row'); element_server.element_row = element_serverrow; element_server.appendChild(element_serverrow); // Current status and percentage let element_status = document.createElement('div'); element_status.classList.add('trweb_torrentliststatus', 'col-12'); element_status.setAttributeNS('trweb', 'server', srv); element_status.setAttributeNS('trweb', 'torrentlistfield', 'status'); element_server.element_status = element_status; element_serverrow.appendChild(element_status); let element_start = document.createElement('div'); element_start.classList.add('trweb_torrentlistcontrol', 'col-4'); element_start.setAttributeNS('trweb', 'server', srv); element_start.setAttributeNS('trweb', 'torrent', hash); element_start.setAttributeNS('trweb', 'torrentlistfield', 'start'); element_start.appendChild(document.createTextNode("Start")); element_start.addEventListener("click", this.#handler.bind(this)); element_server.element_start = element_start; element_serverrow.appendChild(element_start); let element_pause = document.createElement('div'); element_pause.classList.add('trweb_torrentlistcontrol', 'col-4'); element_pause.setAttributeNS('trweb', 'server', srv); element_pause.setAttributeNS('trweb', 'torrent', hash); element_pause.setAttributeNS('trweb', 'torrentlistfield', 'pause'); element_pause.appendChild(document.createTextNode("Pause")); element_pause.addEventListener("click", this.#handler.bind(this)); element_server.element_pause = element_pause; element_serverrow.appendChild(element_pause); // Download by magnet let element_magnet = document.createElement('div'); element_magnet.classList.add('trweb_torrentlistmagnet', 'col'); element_magnet.setAttributeNS('trweb', 'server', srv); element_magnet.setAttributeNS('trweb', 'torrent', hash); element_magnet.setAttributeNS('trweb', 'torrentlistfield', 'magnet'); element_magnet.appendChild(document.createTextNode(`[${srv}] Magnet`)); element_magnet.addEventListener("click", this.#handler.bind(this)); element_server.element_magnet = element_magnet; element_serverrow.appendChild(element_magnet); // Download by torrent file let element_download = document.createElement('div'); element_download.classList.add('trweb_torrentlistdownload', 'col'); element_download.setAttributeNS('trweb', 'server', srv); element_download.setAttributeNS('trweb', 'torrent', hash); element_download.setAttributeNS('trweb', 'torrentlistfield', 'download'); element_download.appendChild(document.createTextNode(`[${srv}] Get torrent`)); element_download.addEventListener("click", this.#handler.bind(this)); element_server.element_download = element_download; element_serverrow.appendChild(element_download); } #updateTorrentDisplay(torrent) { let txt_name = torrent.element_name; nukeChildren(txt_name); txt_name.appendChild(document.createTextNode(torrent.name)); for (const srv of Object.keys(this.#servers)) { let server = torrent.element_server[srv]; let status = server.element_status; server.classList.remove( 'trweb_status_asdf', 'trweb_status_offline', 'trweb_status_nonexistent', 'trweb_status_paused', 'trweb_status_verifqueued', 'trweb_status_verifying', 'trweb_status_downloading', 'trweb_status_seeding' ); nukeChildren(status); let statustext = 'Nothing to see here'; let statusclass = 'trweb_status_asdf' let barwidth = 50; if (!this.#servers[srv].isOnline()) { statustext = "Server offline"; statusclass = 'trweb_status_offline'; barwidth = 0; } else if (torrent.servers[srv] == undefined || torrent.servers[srv].deleted) { statustext = "Not available"; statusclass = 'trweb_status_nonexistent'; barwidth = 0; } else { statustext = `${torrent.servers[srv].status}`; barwidth = torrent.servers[srv].percentDone; switch (torrent.servers[srv].status) { case 0: statustext = 'Paused'; statusclass = 'trweb_status_paused'; break; case 1: statustext = 'Queued for verification'; statusclass = 'trweb_status_verifqueued'; break; case 2: statustext = 'Verifying'; statusclass = 'trweb_status_verifying'; break; case 3: statustext = 'Queued'; break; case 4: statustext = 'Downloading'; statusclass = 'trweb_status_downloading'; break; case 6: statustext = 'Seeding'; statusclass = 'trweb_status_seeding'; break; } statustext = `[${srv}]: ${statustext} - ${torrent.servers[srv].percentDone}%`; } server.element_statusbar.style.width = `${barwidth}%`; server.classList.add(statusclass); status.appendChild(document.createTextNode(statustext)); } } #handler(e) { switch (e.type) { case 'click': if (e.currentTarget.hasAttributeNS('trweb', 'torrentlistfield')) { let hash; switch (e.currentTarget.getAttributeNS('trweb', 'torrentlistfield')) { case 'entry': e.currentTarget.classList.toggle('trweb_hide_buttons'); e.stopPropagation(); break; case 'start': hash = e.currentTarget.getAttributeNS('trweb', 'torrent'); this.#servers[e.currentTarget.getAttributeNS('trweb', 'server')].torrent_start(hash); e.stopPropagation(); break; case 'pause': hash = e.currentTarget.getAttributeNS('trweb', 'torrent'); this.#servers[e.currentTarget.getAttributeNS('trweb', 'server')].torrent_pause(hash); e.stopPropagation(); break; case 'magnet': let magneturl; hash = e.currentTarget.getAttributeNS('trweb', 'torrent'); for (const [srv, data] of Object.entries(this.#knownTorrents[hash].servers)) { if (data.magnetLink != null) { magneturl = data.magnetLink; break; } } if (magneturl != null) { this.#servers[e.currentTarget.getAttributeNS('trweb', 'server')].torrent_add_url(magneturl, false, null); } else { this.#logger(`Couldn't find magnet link! ${JSON.stringify(this.#knownTorrents[hash].servers)}`); } e.stopPropagation(); break; case 'download': e.stopPropagation(); window.alert('not implemented'); break; } } break; case 'DOMContentLoaded': this.#logger('Starting TRWEB instance'); this.#container = document.getElementById(this.#container); this.#container.classList.add('trweb_container'); this.#guiHeader = document.createElement('div'); this.#guiHeader.classList.add('trweb_header'); this.#container.appendChild(this.#guiHeader); this.#torrentListView = document.createElement('div'); this.#torrentListView.classList.add('trweb_torrentlistview', 'container-fluid'); this.#container.appendChild(this.#torrentListView); this.#guiFooter = document.createElement('div'); this.#guiFooter.classList.add('trweb_footer'); this.#container.appendChild(this.#guiFooter); this.#loadServers(); this.#saveServers(); this.setTimer(); break; } } refresh() { for (const [key, value] of Object.entries(this.#servers)) { //value.refreshSession(); value.refreshTorrentList(); } } sort() { let list = Object.values(this.#knownTorrents); list.sort((a, b) => a.name.localeCompare(b.name)); for (const node of list) { this.#torrentListView.appendChild(node.element); } return list; } #timercb() { if (this.#needsSort) { this.#needsSort = false; this.sort(); } this.refresh(); } setTimer() { this.#timer = window.setInterval(this.#timercb.bind(this), 2000); } haltTimer() { window.clearInterval(this.#timer); } save() { this.#saveServers(); } rpc(server, method, args) { this.#servers[server].rpccall_debug(method, args); } addServer(name, url, user, pass) { this.#registerServer(name, {'rpcurl': url, 'auth': `Basic ${btoa(`${user}:${pass}`)}`}); for (const hash of Object.keys(this.#knownTorrents)) { this.#createServerEntryForTorrent(hash, name); } this.#saveServers(); } removeServer(name) { delete this.#servers[name]; for (const [hash, entry] of Object.entries(this.#knownTorrents)) { entry.element.removeChild(entry.element_server[name]); delete entry.servers[name]; delete entry.element_server[name] } this.#saveServers(); } } var trinstance = new trweb('trcontainer', null, null, function(msg) {console.info(`TRWEB: ${msg}`)}); //var trinstance = new trweb('trcontainer', null, null, null);