« get me outta code hell

socket log messages & misc npm updates - mtui - Music Text User Interface - user-friendly command line music player
about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFlorrie <towerofnix@gmail.com>2020-07-17 18:48:32 -0300
committerFlorrie <towerofnix@gmail.com>2020-07-17 18:48:32 -0300
commit4b171a6a280b7f9927e95a72470daed551aa6e33 (patch)
tree159ad1c66a55a78968e72440620ffcd70c95ef94
parenta2437eafb1a2d7f39c7e816a693ec4f03654b636 (diff)
socket log messages & misc npm updates
-rw-r--r--backend.js5
-rw-r--r--package-lock.json46
-rw-r--r--package.json6
-rw-r--r--socket.js131
-rw-r--r--ui.js64
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