From 4b171a6a280b7f9927e95a72470daed551aa6e33 Mon Sep 17 00:00:00 2001 From: Florrie Date: Fri, 17 Jul 2020 18:48:32 -0300 Subject: socket log messages & misc npm updates --- backend.js | 5 +++ package-lock.json | 46 +++++-------------- package.json | 6 +-- socket.js | 131 ++++++++++++++++++++++++++++++++++++++++++++---------- ui.js | 64 ++++++++++++++++++++++++-- 5 files changed, 187 insertions(+), 65 deletions(-) diff --git a/backend.js b/backend.js index 67c6335..f2c4d59 100644 --- a/backend.js +++ b/backend.js @@ -298,6 +298,11 @@ class QueuePlayer extends EventEmitter { return focusItem } + replaceAllItems(newItems) { + this.queueGrouplike.items = newItems + this.emitQueueUpdated() + } + clearQueuePast(track) { const { items } = this.queueGrouplike const index = items.indexOf(track) + 1 diff --git a/package-lock.json b/package-lock.json index f984dce..d1887d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,9 +38,12 @@ "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==" }, "is-wsl": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz", - "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "requires": { + "is-docker": "^2.0.0" + } }, "minimist": { "version": "1.2.5", @@ -71,9 +74,9 @@ "integrity": "sha512-rMaLlHV5BlnRhIl6jUfgqdLY5U0NJkIxUdOsmpz3Txwh7js4+GwTiomhO8W4rp3SvX1zZ56mx13zfEWESr+qqA==" }, "open": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/open/-/open-7.0.3.tgz", - "integrity": "sha512-sP2ru2v0P290WFfv49Ap8MF6PkzGNnGlAwHweB4WR4mr5d2d0woiCluUeJ218w7/+PmoBy9JmYgD5A4mLcWOFA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/open/-/open-7.0.4.tgz", + "integrity": "sha512-brSA+/yq+b08Hsr4c8fsEW2CRzk1BmfN3SAK/5VCHQ9bdoZJ4qa/+AfR0xHjlbbZUyPkUHs1b8x1RqdyZdkVqQ==", "requires": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" @@ -118,31 +121,11 @@ } }, "tui-lib": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tui-lib/-/tui-lib-0.2.1.tgz", - "integrity": "sha512-AHyhA9neF8tM5dAJnggKIO1W0w5pSVjuuYryp/bMJee6ol2kIzd8p4mbri0Es6/BP9bvPdYFjhSddWwzAE0TpQ==", - "requires": { - "wcwidth": "^1.0.1", - "word-wrap": "^1.2.3" - } - }, - "tui-text-editor": { "version": "0.3.1", - "resolved": "https://registry.npmjs.org/tui-text-editor/-/tui-text-editor-0.3.1.tgz", - "integrity": "sha512-ySLdKfUHwxt6W1hub7Qt7smtuwujRHWxMIwdnO+IOzhd2B9naIg07JDr2LISZ3X+SZg0mvBNcGGeTf+L8bcSpw==", + "resolved": "https://registry.npmjs.org/tui-lib/-/tui-lib-0.3.1.tgz", + "integrity": "sha512-uCE2j351/b4C2Q3eEhC54EvZiWbgJ/Q3gH5ElS2D+mvRmWbHDzXbPUhcXrx8oOA5rZFZ4iNVMCoLCqzWWZTJyQ==", "requires": { - "tui-lib": "^0.1.1" - }, - "dependencies": { - "tui-lib": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/tui-lib/-/tui-lib-0.1.1.tgz", - "integrity": "sha512-QAE4axNCJ42IZSNnc2pLOkFtzHqYFgenDyw88JHHRNd8PXTVO8+JIpJArpgAguopd4MmoYaJbreze0BHoWMXfA==", - "requires": { - "wcwidth": "^1.0.1", - "word-wrap": "^1.2.3" - } - } + "wcwidth": "^1.0.1" } }, "unique-string": { @@ -165,11 +148,6 @@ "requires": { "defaults": "^1.0.3" } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" } } } diff --git a/package.json b/package.json index 094f101..4ccec3d 100644 --- a/package.json +++ b/package.json @@ -14,12 +14,10 @@ "mkdirp": "^0.5.5", "node-fetch": "^2.6.0", "node-natural-sort": "^0.8.7", - "open": "^7.0.3", + "open": "^7.0.4", "sanitize-filename": "^1.6.3", "shortid": "^2.2.15", "tempy": "^0.2.1", - "tui-lib": "^0.2.1", - "tui-text-editor": "^0.3.1", - "word-wrap": "^1.2.3" + "tui-lib": "^0.3.1" } } diff --git a/socket.js b/socket.js index b418853..a092e4a 100644 --- a/socket.js +++ b/socket.js @@ -10,6 +10,11 @@ 'use strict' // single quotes & no semicolons time babey +// This is expected to be the same across both the client and the server. +// There will probably be inconsistencies between sender clients and receiving +// clients / the server otherwise. +const DEFAULT_NICKNAME = '(Unnamed)' + const EventEmitter = require('events') const net = require('net') @@ -22,6 +27,7 @@ const { } = require('./serialized-backend') const { + getTimeStringsFromSec, silenceEvents } = require('./general-util') @@ -96,7 +102,7 @@ function validateCommand(command) { case 'clear-queue-up-to': return ( typeof command.queuePlayer === 'string' && - isItemRef(command.topItem) + isItemRef(command.track) ) case 'distribute-queue': return ( @@ -138,7 +144,8 @@ function validateCommand(command) { return ( typeof command.queuePlayer === 'string' && Array.isArray(command.tracks) && - command.tracks.every(track => isItemRef(track)) + command.tracks.every(track => isItemRef(track)) && + ['shuffle'].includes(command.why) ) case 'seek-to': return ( @@ -148,6 +155,7 @@ function validateCommand(command) { case 'set-nickname': return ( typeof command.nickname === 'string' && + typeof command.oldNickname === 'string' && command.nickname.length >= 1 && command.nickname.length <= 12 ) @@ -225,7 +233,7 @@ function makeSocketServer() { server.on('connection', socket => { sockets.push(socket) - let nickname = '(Unnamed)' + let nickname = DEFAULT_NICKNAME socket.on('close', () => { if (sockets.includes(socket)) { @@ -261,7 +269,7 @@ function makeSocketServer() { readySockets.push(socket) if (readySockets.length === sockets.length) { for (const socket of sockets) { - socket.write(JSON.stringify({ + socket.write(serializeCommandToData({ sender: 'server', code: 'set-pause', queuePlayer: command.queuePlayer, @@ -277,13 +285,13 @@ function makeSocketServer() { case 'sync-playback': for (const QP of server.canonicalBackend.queuePlayers) { if (QP.timeData) { - socket.write(JSON.stringify({ + socket.write(serializeCommandToData({ sender: 'server', code: 'seek-to', queuePlayer: QP.id, time: QP.timeData.curSecTotal }) + '\n') - socket.write(JSON.stringify({ + socket.write(serializeCommandToData({ sender: 'server', code: 'set-pause', queuePlayer: QP.id, @@ -304,8 +312,11 @@ function makeSocketServer() { } // If it's a 'set-nickname' command, save the nickname. + // Also attach the old nickname for display in log messages. if (command.code === 'set-nickname') { + command.oldNickname = nickname + command.senderNickname = nickname nickname = command.nickname } @@ -314,7 +325,7 @@ function makeSocketServer() { const otherSockets = sockets.filter(s => s !== socket) for (const socket of otherSockets) { - socket.write(JSON.stringify(command) + '\n') + socket.write(serializeCommandToData(command) + '\n') } })) @@ -326,7 +337,7 @@ function makeSocketServer() { } } - socket.write(JSON.stringify({ + socket.write(serializeCommandToData({ sender: 'server', code: 'initialize-backend', backend: savedBackend @@ -343,6 +354,7 @@ function makeSocketClient() { const client = new EventEmitter() client.socket = new net.Socket() + client.nickname = DEFAULT_NICKNAME client.sendCommand = function(command) { const data = serializeCommandToData(command) @@ -351,7 +363,9 @@ function makeSocketClient() { } client.setNickname = function(nickname) { - client.sendCommand({code: 'set-nickname', nickname}) + let oldNickname = client.nickname + client.nickname = nickname + client.sendCommand({code: 'set-nickname', nickname, oldNickname}) } client.socket.on('data', perLine(line => { @@ -384,11 +398,78 @@ function attachBackendToSocketClient(backend, client, { backend.setAlwaysStartPaused(true) function logCommand(command) { - const nickname = command.sender === 'server' ? 'the server' : command.nickname - backend.showLogMessage(`${nickname} sent ${command.code}!`) + const nickToMessage = nickname => `\x1b[32;1m${nickname}\x1b[0m` + const itemToMessage = item => `\x1b[32m"${item.name}"\x1b[0m` + let fullmsg = '' // may be overridden + let actionmsg = `sent ${command.code}` // fallback + switch (command.code) { + case 'clear-queue': + actionmsg = 'cleared the queue' + break + case 'clear-queue-past': + actionmsg = `cleared the queue past ${itemToMessage(command.track)}` + break + case 'clear-queue-up-to': + actionmsg = `cleared the queue up to ${itemToMessage(command.track)}` + break + case 'distribute-queue': + actionmsg = `distributed ${itemToMessage(command.topItem)} across the queue ${command.opts.how}` + break + case 'initialize-backend': + return + case 'play': + actionmsg = `started playing ${itemToMessage(command.track)}` + break + case 'queue': { + let afterMessage = '' + if (isItemRef(command.afterItem)) { + afterMessage = ` after ${itemToMessage(command.afterItem)}` + } else if (command.afterItem === 'FRONT') { + afterMessage = ` at the front of the queue` + } + actionmsg = `queued ${itemToMessage(command.topItem)}` + afterMessage + break + } + case 'restore-queue': + if (command.why === 'shuffle') { + actionmsg = 'shuffled the queue' + } + break + case 'seek-to': + // TODO: the second value here should be the duration of the track + // (this will make values like 0:0x:yy / 1:xx:yy appear correctly) + actionmsg = `seeked to ${getTimeStringsFromSec(command.time, command.time).timeDone}` + break + case 'set-nickname': + fullmsg = `${nickToMessage(command.nickname)} updated their nickname (from ${nickToMessage(command.oldNickname)})` + break + case 'set-pause': + if (command.paused) { + actionmsg = 'paused the player' + } else { + actionmsg = 'resumed the player' + } + break + case 'stop-playing': + actionmsg = 'stopped the player' + break + case 'unqueue': + actionmsg = `removed ${itemToMessage(command.topItem)} from the queue` + break + case 'status': + return + } + if (!fullmsg) { + const nickname = command.sender === 'server' ? 'the server' : command.senderNickname + fullmsg = `${nickToMessage(nickname)} ${actionmsg}` + } + backend.showLogMessage(fullmsg) } - client.on('sent-command', logCommand) + client.on('sent-command', command => { + command.senderNickname = client.nickname + logCommand(command) + }) client.on('command', async command => { logCommand(command) @@ -419,17 +500,17 @@ function attachBackendToSocketClient(backend, client, { return case 'clear-queue-past': if (QP) silenceEvents(QP, ['clear-queue-past'], () => QP.clearQueuePast( - restoreNewItem(command.topItem, getPlaylistSources()) + restoreNewItem(command.track, getPlaylistSources()) )) return case 'clear-queue-up-to': if (QP) silenceEvents(QP, ['clear-queue-up-to'], () => QP.clearQueueUpTo( - restoreNewItem(command.topItem, getPlaylistSources()) + restoreNewItem(command.track, getPlaylistSources()) )) return case 'distribute-queue': if (QP) silenceEvents(QP, ['distribute-queue'], () => QP.distributeQueue( - restoreNewItem(command.topItem, getPlaylistSources()), + restoreNewItem(command.topItem), { how: command.opts.how, rangeEnd: command.opts.rangeEnd @@ -452,7 +533,7 @@ function attachBackendToSocketClient(backend, client, { return case 'queue': if (QP) silenceEvents(QP, ['queue'], () => QP.queue( - restoreNewItem(command.topItem, getPlaylistSources()), + restoreNewItem(command.topItem), isItemRef(command.afterItem) ? restoreNewItem(command.afterItem, getPlaylistSources()) : command.afterItem, { movePlayingTrack: command.opts.movePlayingTrack @@ -461,14 +542,17 @@ function attachBackendToSocketClient(backend, client, { return case 'restore-queue': if (QP) { - QP.queueGrouplike.items = command.tracks.map(refData => restoreNewItem(refData)) - // TODO: target just the one queue player. hacks = illegal - updateRestoredTracksUsingPlaylists(backend, getPlaylistSources()) + QP.replaceAllItems(command.tracks.map( + refData => restoreNewItem(refData, getPlaylistSources()) + )) } + return case 'seek-to': if (QP) silenceEvents(QP, ['seek-to'], () => QP.seekTo(command.time)) return case 'set-pause': { + // TODO: there's an event leak here when toggling pause while + // nothing is playing let playingThisTrack = true QP.once('playing new track', () => { playingThisTrack = false @@ -500,19 +584,19 @@ function attachBackendToSocketClient(backend, client, { }) }) - backend.on('clear-queue-past', (queuePlayer, topItem) => { + backend.on('clear-queue-past', (queuePlayer, track) => { client.sendCommand({ code: 'clear-queue-past', queuePlayer: queuePlayer.id, - topItem: saveItemReference(topItem) + track: saveItemReference(track) }) }) - backend.on('clear-queue-up-to', (queuePlayer, topItem) => { + backend.on('clear-queue-up-to', (queuePlayer, track) => { client.sendCommand({ code: 'clear-queue-up-to', queuePlayer: queuePlayer.id, - topItem: saveItemReference(topItem) + track: saveItemReference(track) }) }) @@ -572,6 +656,7 @@ function attachBackendToSocketClient(backend, client, { backend.on('shuffle-queue', queuePlayer => { client.sendCommand({ code: 'restore-queue', + why: 'shuffle', queuePlayer: queuePlayer.id, tracks: queuePlayer.queueGrouplike.items.map(saveItemReference) }) diff --git a/ui.js b/ui.js index 2777326..6562139 100644 --- a/ui.js +++ b/ui.js @@ -1500,19 +1500,28 @@ class AppElement extends FocusElement { this.SQP.playNext(playingTrack) } + const oldName = item.name if (isGroup(item)) { if (order === 'shuffle') { - item = {items: shuffleArray(flattenGrouplike(item).items)} + item = { + name: `${oldName} (shuffled)`, + items: shuffleArray(flattenGrouplike(item).items) + } } else if (order === 'shuffle-groups') { item = shuffleOrderOfGroups(item) + item.name = `${oldName} (group order shuffled)` } else if (order === 'reverse') { - item = {items: flattenGrouplike(item).items.reverse()} + item = { + name: `${oldName} (reversed)`, + items: flattenGrouplike(item).items.reverse() + } } else if (order === 'reverse-groups') { item = reverseOrderOfGroups(item) + item.name = `${oldName} (group order reversed)` } } else { // Make it into a grouplike that just contains itself. - item = {items: [item]} + item = {name: oldName, items: [item]} } if (where === 'next' || where === 'next-selected' || where === 'end') { @@ -4396,6 +4405,10 @@ class Log extends ListScrollForm { } newLogMessage(text) { + if (this.inputs.length === 10) { + this.removeInput(this.inputs[0]) + } + const logMessage = new LogMessage(text) this.addInput(logMessage) this.fixLayout() @@ -4404,6 +4417,49 @@ class Log extends ListScrollForm { } } -class LogMessage extends Button {} +class LogMessage extends FocusElement { + constructor(text) { + super() + + this.label = new LogMessageLabel(text) + this.addChild(this.label) + } + + fixLayout() { + this.w = this.parent.contentW + this.label.w = this.contentW + this.h = this.label.h + } + + clicked(button) { + if (button === 'left') { + this.root.select(this) + return false + } + } +} + +class LogMessageLabel extends WrapLabel { + writeTextTo(writable) { + const w = this.w + const lines = this.getWrappedLines() + for (let i = 0; i < lines.length; i++) { + const text = this.processFormatting(lines[i]) + writable.write(ansi.moveCursor(this.absTop + i, this.absLeft)) + writable.write(text) + const width = ansi.measureColumns(text) + if (width < w && this.textAttributes.length) { + writable.write(ansi.setAttributes([ansi.A_RESET, ...this.textAttributes])) + writable.write(' '.repeat(w - width)) + } + } + } + + set textAttributes(val) {} + + get textAttributes() { + return this.parent.isSelected ? [40] : [] + } +} module.exports = AppElement -- cgit 1.3.0-6-gf8a5