« get me outta code hell

allow lines spread across multiple data chunks - 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-12 17:50:21 -0300
committer(quasar) nebula <qznebula@protonmail.com>2024-05-16 19:04:12 -0300
commit1f916d3acd03acdc67699e24015562a3d2422f27 (patch)
tree1dc7a8f68f1db9ef06a96e03f5bcbad9f781f637
parenta0c63e60f80d5d8a881fa98015b54b9803529bb9 (diff)
allow lines spread across multiple data chunks
Wrapper functions 4 lyfe.
-rw-r--r--socket.js165
1 files changed, 92 insertions, 73 deletions
diff --git a/socket.js b/socket.js
index 52efb4d..e56f999 100644
--- a/socket.js
+++ b/socket.js
@@ -174,6 +174,29 @@ function validateCommand(command) {
   return false
 }
 
+function perLine(handleLine) {
+  // Wrapper function to run a callback for each line provided to the wrapped
+  // callback. Maintains a "partial" variable so that a line may be broken up
+  // into multiple chunks before it is sent. Also supports handling multiple
+  // lines (including the conclusion to a previously received partial line)
+  // being received at once.
+
+  let partial = ''
+  return data => {
+    const text = data.toString()
+    const lines = text.split('\n')
+    if (lines.length === 1) {
+      partial += text
+    } else {
+      handleLine(partial + lines[0])
+      for (const line of lines.slice(1, -1)) {
+        handleLine(line)
+      }
+      partial = lines[lines.length - 1]
+    }
+  }
+}
+
 export function makeSocketServer() {
   // The socket server has two functions: to maintain a "canonical" backend
   // and synchronize newly connected clients with the relevent data in this
@@ -200,85 +223,83 @@ export function makeSocketServer() {
       }
     })
 
-    socket.on('data', data => {
+    socket.on('data', perLine(line => {
       // Parse data as a command and validate it. If invalid, drop this data.
 
-      for (const line of data.toString().trim().split('\n')) {
-        let command
-        try {
-          command = deserializeDataToCommand(line)
-        } catch (error) {
-          return
-        }
+      let command
+      try {
+        command = deserializeDataToCommand(line)
+      } catch (error) {
+        return
+      }
 
-        command.sender = 'client'
+      command.sender = 'client'
 
-        if (!validateCommand(command)) {
-          return
-        }
+      if (!validateCommand(command)) {
+        return
+      }
 
-        // If it's a status command, respond appropriately, and return so that it
-        // is not relayed.
-
-        if (command.code === 'status') {
-          switch (command.status) {
-            case 'ready-to-resume': {
-              const readySockets = readyToResume[command.queuePlayer]
-              if (readySockets && !readySockets.includes(socket)) {
-                readySockets.push(socket)
-                if (readySockets.length === sockets.length) {
-                  for (const socket of sockets) {
-                    socket.write(JSON.stringify({
-                      sender: 'server',
-                      code: 'set-pause',
-                      queuePlayer: command.queuePlayer,
-                      startingTrack: true,
-                      paused: false
-                    }) + '\n')
-                  }
-                  delete readyToResume[command.queuePlayer]
-                }
-              }
-              break
-            }
-            case 'sync-playback':
-              for (const QP of server.canonicalBackend.queuePlayers) {
-                if (QP.timeData) {
-                  socket.write(JSON.stringify({
-                    sender: 'server',
-                    code: 'seek-to',
-                    queuePlayer: QP.id,
-                    time: QP.timeData.curSecTotal
-                  }) + '\n')
+      // If it's a status command, respond appropriately, and return so that it
+      // is not relayed.
+
+      if (command.code === 'status') {
+        switch (command.status) {
+          case 'ready-to-resume': {
+            const readySockets = readyToResume[command.queuePlayer]
+            if (readySockets && !readySockets.includes(socket)) {
+              readySockets.push(socket)
+              if (readySockets.length === sockets.length) {
+                for (const socket of sockets) {
                   socket.write(JSON.stringify({
                     sender: 'server',
                     code: 'set-pause',
-                    queuePlayer: QP.id,
+                    queuePlayer: command.queuePlayer,
                     startingTrack: true,
-                    paused: QP.player.isPaused
+                    paused: false
                   }) + '\n')
                 }
+                delete readyToResume[command.queuePlayer]
               }
-              break
+            }
+            break
           }
-          return
+          case 'sync-playback':
+            for (const QP of server.canonicalBackend.queuePlayers) {
+              if (QP.timeData) {
+                socket.write(JSON.stringify({
+                  sender: 'server',
+                  code: 'seek-to',
+                  queuePlayer: QP.id,
+                  time: QP.timeData.curSecTotal
+                }) + '\n')
+                socket.write(JSON.stringify({
+                  sender: 'server',
+                  code: 'set-pause',
+                  queuePlayer: QP.id,
+                  startingTrack: true,
+                  paused: QP.player.isPaused
+                }) + '\n')
+              }
+            }
+            break
         }
+        return
+      }
 
-        // If it's a 'play' command, set up a new readyToResume array.
+      // If it's a 'play' command, set up a new readyToResume array.
 
-        if (command.code === 'play') {
-          readyToResume[command.queuePlayer] = []
-        }
+      if (command.code === 'play') {
+        readyToResume[command.queuePlayer] = []
+      }
 
-        // Relay the command to client sockets besides the sender.
+      // Relay the command to client sockets besides the sender.
 
-        const otherSockets = sockets.filter(s => s !== socket)
+      const otherSockets = sockets.filter(s => s !== socket)
 
-        for (const socket of otherSockets) {
-          socket.write(JSON.stringify(command) + '\n')
-        }
+      for (const socket of otherSockets) {
+        socket.write(JSON.stringify(command) + '\n')
       }
-    })
+    }))
 
     const savedBackend = saveBackend(server.canonicalBackend)
 
@@ -311,25 +332,23 @@ export function makeSocketClient() {
     client.socket.write(data + '\n')
   }
 
-  client.socket.on('data', data => {
+  client.socket.on('data', perLine(line => {
     // Same sort of "guarding" deserialization/validation as in the server
     // code, because it's possible the client and server backends mismatch.
 
-    for (const line of data.toString().trim().split('\n')) {
-      let command
-      try {
-        command = deserializeDataToCommand(line)
-      } catch (error) {
-        return
-      }
-
-      if (!validateCommand(command)) {
-        return
-      }
+    let command
+    try {
+      command = deserializeDataToCommand(line)
+    } catch (error) {
+      return
+    }
 
-      client.emit('command', command)
+    if (!validateCommand(command)) {
+      return
     }
-  })
+
+    client.emit('command', command)
+  }))
 
   return client
 }