diff --git a/js/trdom_servermanager.js b/js/trdom_servermanager.js index b4bef39..0e5dd70 100644 --- a/js/trdom_servermanager.js +++ b/js/trdom_servermanager.js @@ -15,12 +15,25 @@ class trdom_servermanager extends EventTarget { } #registerServer = function(name, initdata) { - let server = new trserver(initdata, function(msg) {this.#logcb(`server:${name}`, 6, msg);}.bind(this)); + let server = new trserver(name, initdata, this.#logcb); this.#servers[name] = server; - server.addEventListener('torrent-update', this); + //server.addEventListener('torrent-updated', this); + server.addEventListener('torrent-initialize', this); + let e = new CustomEvent('torrentserver-added', { + detail: { + serverName: name, + serverObject: server + } + }); + this.dispatchEvent(e); server.refreshTorrentList(); } + addServer(name, url, user, pass) { + this.#registerServer(name, {'rpcurl': url, 'auth': `Basic ${btoa(`${user}:${pass}`)}`}); + this.#saveServers(); + } + #loadcb; #loadServers() { let serverjson = this.#loadcb('servers'); @@ -53,35 +66,31 @@ class trdom_servermanager extends EventTarget { this.#saveServers(); } - constructor (loadcb = null, savecb = null, logcb = null, registerServer = null) { + constructor (loadcb = null, savecb = null, logcb = null) { super(); this.#loadcb = loadcb; this.#savecb = savecb; this.#logcb = logcb - - //this.#registerServer = registerServer; - - //this.#servers = this.#loadServers(); } handleEvent(e) { this.#log(6, `Handling event of type ${e.type}`); switch (e.type) { - case 'torrent-update': + case 'torrent-initialize': + let initControls = {}; + let initHash = e.detail.torrentHash; for (const [srv, server] of Object.entries(this.#servers)) { - if (e.target == server) { - let newEvent = new CustomEvent('torrent-update', { - 'detail': { - 'torrentHash': e.detail.torrentHash, - 'torrentInfo': e.detail.torrentInfo, - 'serverName': srv - } - }); - this.dispatchEvent(newEvent); - return; - } + let control = server == e.detail.serverObject ? e.detail.torrentControl : new trdom_torrentcontrol(initHash, server, null, this.#logcb); + //server.addControl(control); + initControls[srv] = control; } - this.#log(2, `Got update event for unknown server:\n${JSON.stringify(e.detail)}`); + let initEvent = new CustomEvent('torrent-created', { + detail: { + torrentHash: initHash, + torrentControls: initControls + } + }); + this.dispatchEvent(initEvent); break; default: this.#log(5, `Event type ${e.type} not supported`); diff --git a/js/trdom_torrentcontrol.js b/js/trdom_torrentcontrol.js index 3f92669..b1d66a6 100644 --- a/js/trdom_torrentcontrol.js +++ b/js/trdom_torrentcontrol.js @@ -1,25 +1,98 @@ "use strict"; -class trdom_torrentcontrol { +class trdom_torrentcontrol extends EventTarget { #logcb; #log(loglevel, msg) { if (this.#logcb !== null) { - this.#logcb('trdom_servermanager', loglevel, msg); + this.#logcb(`server:${this.#server.name}:${this.#hash}`, loglevel, msg); } } #element = document.createElement('div'); - getElement() { + get element() { return this.#element; } #hash; #server; - //TODO: remove - #getKnownTorrents; + #data; + get exists() { + return this.#data != null; + } + + updateDOM() { + let control_element = this.#element; + let status = control_element.element_status; + + control_element.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.#server.isOnline()) { + statustext = "Server offline"; + statusclass = 'trweb_status_offline'; + barwidth = 0; + } + else { + if (!this.exists) { + //this.#log(5, JSON.stringify(this.#data)); + statustext = "Not available"; + statusclass = 'trweb_status_nonexistent'; + barwidth = 0; + } + else { + let server_status = this.#data; + statustext = `${server_status.status}`; + barwidth = server_status.percentDone; + switch (server_status.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 = `[${this.#server.name}]: ${statustext} - ${server_status.percentDone}%`; + } + } + control_element.element_statusbar.style.width = `${barwidth}%`; + control_element.classList.add(statusclass); + status.appendChild(document.createTextNode(statustext)); + } handleEvent(e) { + this.#log(6, `Handling event of type ${e.type}`); switch (e.type) { case 'click': if (e.currentTarget.hasAttributeNS('trweb', 'torrentlistfield')) { @@ -33,7 +106,7 @@ class trdom_torrentcontrol { e.stopPropagation(); break; case 'magnet': - let magneturl; + /*let magneturl; for (const [srv, data] of Object.entries(this.#getKnownTorrents()[this.#hash].servers)) { if (data.magnetLink != null) { magneturl = data.magnetLink; @@ -45,8 +118,9 @@ class trdom_torrentcontrol { } else { this.#log(3, `Couldn't find magnet link! ${JSON.stringify(this.#getKnownTorrents()[this.#hash].servers)}`); - } + }*/ e.stopPropagation(); + window.alert('not implemented'); break; case 'download': e.stopPropagation(); @@ -54,14 +128,27 @@ class trdom_torrentcontrol { break; } } + break; + case 'torrent-deleted': + this.#log(6, 'we be delet'); + this.#data = null; + this.updateDOM(); + break; + case 'torrent-updated': + if (e.detail.torrentHash == this.#hash) { + this.#log(6, `Got data from server: ${JSON.stringify(e.detail.torrentInfo)}`); + this.#data = e.detail.torrentInfo; + this.updateDOM(); + } + break; } } - constructor(hash, server, logcb, getKnownTorrents) { + constructor(hash, server, initdata = null, logcb = null) { + super(); this.#hash = hash; this.#server = server; this.#logcb = logcb; - this.#getKnownTorrents = getKnownTorrents; let srv = server.name; @@ -130,6 +217,16 @@ class trdom_torrentcontrol { this.#element.element_pause = element_pause; this.#element.element_magnet = element_magnet; this.#element.element_download = element_download; + + this.#data = initdata; + + this.updateDOM(); + + this.addEventListener('torrent-updated', this); + + this.addEventListener('torrent-deleted', this); + + server.addControl(hash, this); } } \ No newline at end of file diff --git a/js/trdom_torrentmanager.js b/js/trdom_torrentmanager.js index 7311581..8251e51 100644 --- a/js/trdom_torrentmanager.js +++ b/js/trdom_torrentmanager.js @@ -1,12 +1,56 @@ "use strict"; -class trdom_torrentmanager { +class trdom_torrentmanager extends EventTarget { + #logcb; + #log(loglevel, msg) { + if (this.#logcb !== null) { + this.#logcb('trdom_torrentmanager', loglevel, msg); + } + } + #element = document.createElement('div'); getElement() { return this.#element; } - constructor() { + #torrentDB = {}; + get torrents() { + return this.#torrentDB; + } + + constructor(logcb = null) { + super(); + this.#logcb = logcb; this.#element.classList.add('trweb_torrentlistview', 'container-fluid'); } + + handleEvent(e) { + this.#log(6, `Handling event of type ${e.type}`); + switch (e.type) { + case 'torrent-created': + let initHash = e.detail.torrentHash; + if (this.#torrentDB[initHash] == null) { + let torrentCreated = new trtorrent(initHash); + this.#torrentDB[initHash] = torrentCreated; + this.#element.appendChild(torrentCreated.element); + for (const [srv, control] of Object.entries(e.detail.torrentControls)) { + torrentCreated.addControl(srv, control) + } + } + else { + this.#log(1, 'This event should never ever fire for a torrent that already exists'); + window.alert('We be ded, check console'); + } + break; + case 'torrentserver-added': + let addedServer = e.detail.serverObject; + for (const [hash, torrent] of Object.entries(this.#torrentDB)) { + torrent.addControl(addedServer.name, new trdom_torrentcontrol(hash, addedServer, null, this.#logcb)) + } + break; + default: + this.#log(5, `Event type ${e.type} not supported`); + break; + } + } } diff --git a/js/trserver.js b/js/trserver.js index cd7e4b9..60ad254 100644 --- a/js/trserver.js +++ b/js/trserver.js @@ -1,33 +1,53 @@ "use strict"; class trserver extends EventTarget { + #logcb; + #log(loglevel, msg) { + if (this.#logcb !== null) { + this.#logcb(`server:${this.name}`, loglevel, msg); + } + } + + #name; + get name() { + return this.#name; + } #rpcurl; #authHeader; #sessionHeader; #requests = []; - #torrents = {}; + + #torrentControls = {}; + addControl(hash, control) { + this.#torrentControls[hash] = control; + } + getControl(hash) { + return this.#torrentControls[hash]; + } + #isOnline = false; + #setOnline(online) { + if (this.isOnline == online) { + return; + } + this.#isOnline = online; + let detail = { + }; + let etype; + if (online) { + etype = 'torrentserver-online'; + } + else { + etype = 'torrentserver-offline'; + } + this.dispatchEvent(new CustomEvent(etype, detail)); + } - #logger = function(msg) {}; - #torrentCallback = function(hash, torrentInfo) { - let e = new CustomEvent('torrent-update', { - 'detail': { - 'torrentHash': hash, - 'torrentInfo': torrentInfo - } - }); - this.dispatchEvent(e); - }; - - constructor (initdata, logger = null, tcb = null) { + constructor (name, initdata, logcb = null) { super(); + this.#logcb = logcb; + this.#name = name; this.#rpcurl = initdata.rpcurl; - if (logger != null) { - this.#logger = logger; - } - if (tcb != null) { - this.#torrentCallback = tcb; - } this.#authHeader = initdata.auth; this.#sessionHeader = null; } @@ -63,7 +83,7 @@ class trserver extends EventTarget { try { rpc.send(JSON.stringify(request.payload)); } catch (error) { - this.#logger(error); + this.#log(2, error); } } @@ -83,9 +103,9 @@ class trserver extends EventTarget { switch (e.target.status) { case 200: if (!this.#isOnline) { - this.#logger('Server API connection estabilished'); + this.#log(4, 'Server API connection estabilished'); this.#isOnline = true; - this.#forceUpdateTorrents(); + //this.#forceUpdateTorrents(); } //request.response = JSON.parse(e.target.responseText); if (request.success != null) { @@ -108,42 +128,40 @@ class trserver extends EventTarget { break; case 'timeout': if (this.#isOnline) { - this.#logger('Server API connection timed out'); + this.#log(3, 'Server API connection timed out'); this.#isOnline = false; this.#forceUpdateTorrents(); } break; case 'error': if (this.#isOnline) { - this.#logger('Server API refuses connection'); + this.#log(3, 'Server API refuses connection'); this.#isOnline = false; this.#forceUpdateTorrents(); } - this.#logger(JSON.stringify(e)); + this.#log(5, JSON.stringify(e)); break; } } + #forceUpdateTorrents() { + for (const control of Object.values(this.#torrentControls)) { + control.updateDOM(); + } + } + refreshSession (){ //let tag = this.#rpccall_prepare('session-get', null); //this.#rpccall(tag); } #parseTorrents(response) { - // mark everything as deleted but clean - for (const [key, value] of Object.entries(this.#torrents)) { - value.wasDeleted = value.deleted; - value.deleted = true; - value.dirty = false; - } + let updatedHashes = []; // Update torrent data - for (const torrent of response.torrents) { - let hash = torrent.hashString; - if (!(hash in this.#torrents)) { - this.#torrents[hash] = {'dirty': true}; - } - let knownTorrent = this.#torrents[hash]; - for (const [key, value] of Object.entries(torrent)) { + for (const fromserver of response.torrents) { + let hash = fromserver.hashString; + let initdata = {}; + for (const [key, value] of Object.entries(fromserver)) { let parsedValue = undefined; switch (key) { case 'hashString': @@ -156,30 +174,51 @@ class trserver extends EventTarget { break; } - if (parsedValue != undefined && knownTorrent[key] !== parsedValue) { - knownTorrent[key] = parsedValue; - knownTorrent.dirty = true; + if (parsedValue != undefined) { + initdata[key] = parsedValue; } } - knownTorrent.deleted = false; - } - //check for changes - for (const [key, value] of Object.entries(this.#torrents)) { - if (value.deleted != value.wasDeleted || value.dirty) { - // call UI update - this.#torrentCallback(key, value); - } - } - } - #forceUpdateTorrents() { - for (const [key, value] of Object.entries(this.#torrents)) { - // call UI update - this.#torrentCallback(key, value); + let control; + if (!(hash in this.#torrentControls)) { + control = new trdom_torrentcontrol(hash, this, initdata, this.#logcb); + let createControls = new CustomEvent('torrent-initialize', { + detail: { + torrentHash: hash, + torrentControl: control, + serverObject: this + } + }); + this.dispatchEvent(createControls); + } + else { + control = this.getControl(hash); + } + + let e = new CustomEvent('torrent-updated', { + detail: { + torrentHash: hash, + torrentInfo: initdata + } + }); + control.dispatchEvent(e); + updatedHashes.push(hash); + } + + for (const [hash, control] of Object.entries(this.#torrentControls)) { + if (!updatedHashes.includes(hash) && (control.exists)) { + let e = new CustomEvent('torrent-deleted', { + detail: { + torrentHash: hash + } + }); + control.dispatchEvent(e); + } } } refreshTorrentList() { + let tag = this.#rpccall_prepare('torrent-get', {'fields': ['id', 'hashString', 'name', 'status', 'percentDone', 'comment', 'magnetLink']}); this.#requests[tag].success = this.#parseTorrents.bind(this); this.#rpccall(tag); diff --git a/js/trtorrent.js b/js/trtorrent.js index 7fa71db..b4378c5 100644 --- a/js/trtorrent.js +++ b/js/trtorrent.js @@ -1,21 +1,30 @@ "use strict"; -class trtorrent { +class trtorrent extends EventTarget { #element = document.createElement('div'); - getElement() { + get element() { return this.#element; } #hash; - #name = '## NAME NOT SET ##'; + #name; + get name() { + return this.#name == null ? this.#hash : this.#name; + } + set name(value) { + this.#name = value; + nukeChildren(this.element.element_name); + this.element.element_name.appendChild(document.createTextNode(this.name)); + } - #element_server = {}; + #serverControls = {}; addControl(srv, control) { - this.#element_server[srv] = control; - this.#element.appendChild(control.getElement()); + this.#serverControls[srv] = control; + this.#element.appendChild(control.element); + control.addEventListener('torrent-updated', this); } getControl(srv) { - return this.#element_server[srv]; + return this.#serverControls[srv]; } #servers = {}; @@ -38,10 +47,16 @@ class trtorrent { } } break; + case 'torrent-updated': + if (this.#name == null) { + this.name = e.detail.torrentInfo.name; + } + break; } } constructor(hash) { + super(); this.#hash = hash; // set up DOM structure diff --git a/js/trweb.js b/js/trweb.js index 12260fe..85b5a02 100644 --- a/js/trweb.js +++ b/js/trweb.js @@ -1,7 +1,7 @@ "use strict"; class trweb { - #knownTorrents = {}; + /*#knownTorrents = {};*/ #needsSort = false; @@ -13,6 +13,11 @@ class trweb { this.#logger(`${module}: ${msg}`); } }.bind(this); + #log(loglevel, msg) { + if (this.#logcb !== null) { + this.#logcb('TRWEB', loglevel, msg); + } + } #timer; @@ -22,7 +27,7 @@ class trweb { #dom_menubar = new trdom_menubar(); #dom_servermanager; - #dom_torrentmanagger = new trdom_torrentmanager(); + #dom_torrentmanagger; constructor (container, loadcb, savecb, logger) { this.#container = container; @@ -34,9 +39,19 @@ class trweb { this.#logger = logger; } - this.#dom_servermanager = new trdom_servermanager(this.#loadcb, this.#savecb, this.#logcb, this.#registerServer); + this.#dom_servermanager = new trdom_servermanager(this.#loadcb, this.#savecb, this.#logcb); + this.#dom_torrentmanagger = new trdom_torrentmanager(this.#logcb); - this.#dom_servermanager.addEventListener('torrent-update', this); + //TODO: these should be handled by trdom_torrentmanager + //this.#dom_servermanager.addEventListener('torrent-updated', this); + //this.#dom_servermanager.addEventListener('torrentserver-added', this); + + //this.#dom_servermanager.addEventListener('torrent-updated', this.#dom_torrentmanagger);// for creating torrents when we first get their info + + /* trdom_torrentmanager uses this to add its listeners onto servers */ + this.#dom_servermanager.addEventListener('torrentserver-added', this.#dom_torrentmanagger); + this.#dom_servermanager.addEventListener('torrent-created', this.#dom_torrentmanagger); + //this.#dom_torrentmanagger.addEventListener('torrent-created', this.#dom_servermanager);// for adding server controls to new torrents this.#guiFooter = document.createElement('div'); this.#guiFooter.classList.add('trweb_footer'); @@ -52,45 +67,8 @@ class trweb { } } - #registerServer = function(name, initdata) { - let torrentCallback = function(hash, torrentInfo) { - let mergedTorrentInfo = this.#knownTorrents[hash]; - if (mergedTorrentInfo == null) { - mergedTorrentInfo = this.#createTorrentEntry(hash); - } - mergedTorrentInfo.setStatus(name, torrentInfo); - mergedTorrentInfo.name = torrentInfo.name; - - this.#updateTorrentDisplay(mergedTorrentInfo); - }.bind(this); - let newserver = new trserver(initdata, function(msg) {this.#logger(`[Server: ${name}]: ${msg}`);}.bind(this), torrentCallback); - this.#dom_servermanager.getServers()[name] = newserver; - newserver.refreshTorrentList(); - }.bind(this); - #createTorrentEntry(hash) { - let torrent = new trtorrent(hash); - - this.#knownTorrents[hash] = torrent; - - for (const srv of Object.keys(this.#dom_servermanager.getServers())) { - this.#createServerEntryForTorrent(hash, srv); - } - - this.#dom_torrentmanagger.getElement().appendChild(torrent.getElement()); - this.#updateTorrentDisplay(torrent); - - return torrent; - } - - #createServerEntryForTorrent(hash, srv) { - this.#needsSort = true; - let control = new trdom_torrentcontrol(hash, this.#dom_servermanager.getServers()[srv], this.#logcb, function() {return this.#knownTorrents}.bind(this)); - - let torrent = this.#knownTorrents[hash]; - - torrent.addControl(srv, control); } #updateTorrentDisplay(torrent) { @@ -170,34 +148,39 @@ class trweb { } handleEvent(e) { + this.#log(6, `Handling event of type ${e.type}`); switch (e.type) { - case 'DOMContentLoaded': - this.#logger('Activating TRWEB instance'); - - this.#container = document.getElementById(this.#container); - this.#container.classList.add('trweb_container'); - this.#container.appendChild(this.#dom_menubar.getElement()); - this.#container.appendChild(this.#dom_torrentmanagger.getElement()); - this.#container.appendChild(this.#guiFooter); - - this.#dom_servermanager.loadServers(); - this.#dom_servermanager.saveServers(); - this.setTimer(); - break; - case 'torrent-update': - let mergedTorrentInfo = this.#knownTorrents[e.detail.torrentHash]; - if (mergedTorrentInfo == null) { - mergedTorrentInfo = this.#createTorrentEntry(e.detail.torrentHash); - } - mergedTorrentInfo.setStatus(e.detail.serverName, e.detail.torrentInfo); - mergedTorrentInfo.name = e.detail.torrentInfo.name; + case 'DOMContentLoaded': + this.#logger('Activating TRWEB instance'); + + this.#container = document.getElementById(this.#container); + this.#container.classList.add('trweb_container'); + this.#container.appendChild(this.#dom_menubar.getElement()); + this.#container.appendChild(this.#dom_torrentmanagger.getElement()); + this.#container.appendChild(this.#guiFooter); + + this.#dom_servermanager.loadServers(); + //this.setTimer(); + break; + /*case 'torrent-updated': + this.#log(1, 'making sure'); + let mergedTorrentInfo = this.#knownTorrents[e.detail.torrentHash]; + if (mergedTorrentInfo == null) { + mergedTorrentInfo = this.#createTorrentEntry(e.detail.torrentHash); + } + mergedTorrentInfo.setStatus(e.detail.serverName, e.detail.torrentInfo); + mergedTorrentInfo.name = e.detail.torrentInfo.name; - this.#updateTorrentDisplay(mergedTorrentInfo); - break; + this.#updateTorrentDisplay(mergedTorrentInfo); + break;*/ + default: + this.#log(5, `Event type ${e.type} not supported`); + break; } } refresh() { + this.#log(5,'Refresh'); for (const [key, value] of Object.entries(this.#dom_servermanager.getServers())) { //value.refreshSession(); value.refreshTorrentList(); @@ -205,16 +188,17 @@ class trweb { } sort() { - let list = Object.values(this.#knownTorrents); + let list = Object.values(this.#dom_torrentmanagger.torrents); list.sort((a, b) => a.name.localeCompare(b.name)); for (const torrent of list) { - this.#dom_torrentmanagger.getElement().appendChild(torrent.getElement()); + this.#dom_torrentmanagger.getElement().appendChild(torrent.element); } return list; } #timercb = function() { + this.#log(5,'Timer tick'); if (this.#needsSort) { this.#needsSort = false; this.sort(); @@ -239,16 +223,10 @@ class trweb { } 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.#dom_servermanager.saveServers(); + this.#dom_servermanager.addServer(name, url, user, pass); } - removeServer(name) { + /*removeServer(name) { delete this.#dom_servermanager.getServers()[name]; for (const [hash, entry] of Object.entries(this.#knownTorrents)) { @@ -258,7 +236,7 @@ class trweb { } this.#dom_servermanager.saveServers(); - } + }*/ } var trinstance = new trweb('trcontainer', null, null, function(msg) {console.info(`TRWEB: ${msg}`)}); diff --git a/style/trweb.css b/style/trweb.css index 3f2f180..0bb6a4c 100644 --- a/style/trweb.css +++ b/style/trweb.css @@ -17,7 +17,7 @@ } .trweb_torrentlistserver.trweb_status_offline { - display: none; + visibility: hidden; } .trweb_torrentlistserver > div > * {