karaoke-eternal 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +49 -0
  3. package/assets/app.ico +0 -0
  4. package/assets/app.png +0 -0
  5. package/assets/favicon.ico +0 -0
  6. package/assets/mic-blackTemplate.png +0 -0
  7. package/assets/mic-blackTemplate@2x.png +0 -0
  8. package/assets/mic-white.png +0 -0
  9. package/assets/mic-white@2x.png +0 -0
  10. package/assets/robots.txt +2 -0
  11. package/build/267.4be526e3a94d53aeceae.js +1 -0
  12. package/build/591.4be526e3a94d53aeceae.js +1 -0
  13. package/build/598.4be526e3a94d53aeceae.css +5 -0
  14. package/build/598.4be526e3a94d53aeceae.js +1 -0
  15. package/build/799.4be526e3a94d53aeceae.js +1 -0
  16. package/build/7ce9eb3fe454f54745a4.woff2 +0 -0
  17. package/build/845.4be526e3a94d53aeceae.css +132 -0
  18. package/build/845.4be526e3a94d53aeceae.js +1 -0
  19. package/build/a35814dd9eb496e3d7cc.woff2 +0 -0
  20. package/build/e419b95dccb58b362811.woff2 +0 -0
  21. package/build/index.html +1 -0
  22. package/build/licenses.txt +1400 -0
  23. package/build/main.4be526e3a94d53aeceae.css +2034 -0
  24. package/build/main.4be526e3a94d53aeceae.js +1 -0
  25. package/package.json +144 -0
  26. package/server/Library/Library.js +340 -0
  27. package/server/Library/index.js +3 -0
  28. package/server/Library/ipc.js +18 -0
  29. package/server/Library/router.js +27 -0
  30. package/server/Library/socket.js +47 -0
  31. package/server/Media/Media.js +207 -0
  32. package/server/Media/index.js +3 -0
  33. package/server/Media/ipc.js +19 -0
  34. package/server/Media/router.js +99 -0
  35. package/server/Player/socket.js +78 -0
  36. package/server/Prefs/Prefs.js +165 -0
  37. package/server/Prefs/index.js +3 -0
  38. package/server/Prefs/router.js +124 -0
  39. package/server/Prefs/socket.js +68 -0
  40. package/server/Queue/Queue.js +208 -0
  41. package/server/Queue/index.js +3 -0
  42. package/server/Queue/socket.js +99 -0
  43. package/server/Rooms/Rooms.js +114 -0
  44. package/server/Rooms/index.js +3 -0
  45. package/server/Rooms/router.js +146 -0
  46. package/server/Scanner/FileScanner/FileScanner.js +225 -0
  47. package/server/Scanner/FileScanner/getConfig.js +35 -0
  48. package/server/Scanner/FileScanner/getFiles.js +63 -0
  49. package/server/Scanner/FileScanner/index.js +3 -0
  50. package/server/Scanner/MetaParser/MetaParser.js +49 -0
  51. package/server/Scanner/MetaParser/defaultMiddleware.js +197 -0
  52. package/server/Scanner/MetaParser/index.js +3 -0
  53. package/server/Scanner/Scanner.js +33 -0
  54. package/server/User/User.js +139 -0
  55. package/server/User/index.js +3 -0
  56. package/server/User/router.js +442 -0
  57. package/server/lib/Database.js +55 -0
  58. package/server/lib/IPCBridge.js +115 -0
  59. package/server/lib/Log.js +71 -0
  60. package/server/lib/bcrypt.js +24 -0
  61. package/server/lib/cli.js +136 -0
  62. package/server/lib/electron.js +81 -0
  63. package/server/lib/getCdgName.js +20 -0
  64. package/server/lib/getDevMiddleware.js +51 -0
  65. package/server/lib/getFolders.js +10 -0
  66. package/server/lib/getHotMiddleware.js +27 -0
  67. package/server/lib/getIPAddress.js +16 -0
  68. package/server/lib/getPermutations.js +21 -0
  69. package/server/lib/getWindowsDrives.js +30 -0
  70. package/server/lib/parseCookie.js +12 -0
  71. package/server/lib/pushQueuesAndLibrary.js +29 -0
  72. package/server/lib/schemas/001-initial-schema.sql +98 -0
  73. package/server/lib/schemas/002-replaygain.sql +9 -0
  74. package/server/lib/schemas/003-queue-linked-list.sql +16 -0
  75. package/server/main.js +135 -0
  76. package/server/scannerWorker.js +58 -0
  77. package/server/serverWorker.js +242 -0
  78. package/server/socket.js +173 -0
  79. package/shared/actionTypes.js +103 -0
@@ -0,0 +1,115 @@
1
+ const log = require('./Log')('IPCBridge')
2
+ const {
3
+ _ERROR,
4
+ _SUCCESS,
5
+ } = require('../../shared/actionTypes')
6
+
7
+ const PROCESS_NAME = process.env.KES_CHILD_PROCESS || 'main'
8
+ const children = []
9
+ let handlers = {}
10
+ const reqs = {}
11
+ const isParent = typeof process.env.KES_CHILD_PROCESS === 'undefined' // @todo
12
+ const isChild = !isParent
13
+ let actionId = 0
14
+
15
+ class IPCBridge {
16
+ static send (action) {
17
+ // log.debug(`${PROCESS_NAME} send: `, action.type)
18
+
19
+ if (isChild) {
20
+ process.send(action)
21
+ return
22
+ }
23
+
24
+ if (!children.length) throw new Error('no child processes')
25
+ children.forEach(p => p.send(action))
26
+ }
27
+
28
+ static req (action) {
29
+ const id = ++actionId
30
+ const promise = new Promise((resolve, reject) => {
31
+ reqs[id] = { resolve, reject }
32
+ })
33
+
34
+ action = {
35
+ ...action,
36
+ meta: {
37
+ ...action.meta,
38
+ ipcId: id,
39
+ ipcName: PROCESS_NAME,
40
+ }
41
+ }
42
+
43
+ this.send(action)
44
+
45
+ return promise
46
+ }
47
+
48
+ static _handle (action) {
49
+ const { error, meta, type } = action
50
+
51
+ // is it an ACK for an outstanding request?
52
+ if (meta && meta.ipcName === PROCESS_NAME && reqs[meta.ipcId]) {
53
+ if (error) {
54
+ reqs[meta.ipcId].reject(error)
55
+ } else {
56
+ reqs[meta.ipcId].resolve(action.payload)
57
+ }
58
+
59
+ // log.debug(`${PROCESS_NAME} ack:`, type)
60
+
61
+ delete reqs[meta.ipcId]
62
+ return
63
+ }
64
+
65
+ // log.debug(`${PROCESS_NAME} rcv:`, type)
66
+
67
+ // handle request
68
+ if (!type || typeof handlers[type] !== 'function') {
69
+ log.debug(`${PROCESS_NAME} no handler for action: ${type}`)
70
+ return
71
+ }
72
+
73
+ // @todo handle non-promises?
74
+ handlers[type](action).then(res => {
75
+ if (meta && !meta.noAck) {
76
+ this.send({
77
+ ...action,
78
+ type: type + _SUCCESS,
79
+ payload: res,
80
+ })
81
+ }
82
+ }).catch(err => {
83
+ this.send({
84
+ ...action,
85
+ type: type + _ERROR,
86
+ error: err,
87
+ })
88
+
89
+ log.debug(`${PROCESS_NAME} error in ipc action ${type}: ${err.message}`)
90
+ })
91
+ }
92
+
93
+ static addChild (p) {
94
+ // message from child process
95
+ p.on('message', action => this._handle(action))
96
+ children.push(p)
97
+ }
98
+
99
+ static removeChild (p) { children.splice(children.indexOf(p), 1) }
100
+
101
+ // @todo make real middleware?
102
+ static use (obj) {
103
+ handlers = {
104
+ ...handlers,
105
+ ...obj,
106
+ }
107
+ }
108
+ }
109
+
110
+ module.exports = IPCBridge
111
+
112
+ // make sure IPC channel stays open
113
+ if (isChild) {
114
+ process.on('message', IPCBridge._handle)
115
+ }
@@ -0,0 +1,71 @@
1
+ const util = require('util')
2
+ const _levels = [false, 'error', 'warn', 'info', 'verbose', 'debug']
3
+ let _defaultInstance
4
+
5
+ class Log {
6
+ constructor (logId, cfg) {
7
+ this.logger = require('electron-log').create(logId)
8
+
9
+ // defaults
10
+ this.logger.transports.console.level = 'debug'
11
+ this.logger.transports.file.level = false
12
+ this.logger.transports.file.fileName = logId + '.log'
13
+
14
+ for (const transport in cfg) {
15
+ this.logger.transports[transport].level = cfg[transport]
16
+ }
17
+ }
18
+
19
+ setDefaultInstance () {
20
+ _defaultInstance = this
21
+ return this
22
+ }
23
+
24
+ static resolve (userLevel, defaultLevel) {
25
+ return typeof _levels[userLevel] === 'undefined'
26
+ ? _levels[defaultLevel]
27
+ : _levels[userLevel]
28
+ }
29
+ }
30
+
31
+ class IPCLog {
32
+ constructor (scope = '') {
33
+ const IPC = require('./IPCBridge')
34
+ const { SCANNER_WORKER_LOG } = require('../../shared/actionTypes')
35
+ const send = (level, str, ...args) => {
36
+ IPC.send({
37
+ type: SCANNER_WORKER_LOG,
38
+ payload: {
39
+ level,
40
+ msg: `${scope ? scope + ': ' : ''}${util.format(str, ...args)}`,
41
+ },
42
+ meta: {
43
+ noAck: true,
44
+ }
45
+ })
46
+ }
47
+
48
+ return {
49
+ error: send.bind(this, 'error'),
50
+ warn: send.bind(this, 'warn'),
51
+ info: send.bind(this, 'info'),
52
+ verbose: send.bind(this, 'verbose'),
53
+ debug: send.bind(this, 'debug'),
54
+ }
55
+ }
56
+ }
57
+
58
+ function getLogger (scope = '') {
59
+ if (!_defaultInstance) throw new Error('no default logger instance')
60
+ return _defaultInstance.logger.scope(scope)
61
+ }
62
+
63
+ function getIPCLogger (scope = '') {
64
+ return new IPCLog(scope)
65
+ }
66
+
67
+ // default export
68
+ module.exports = process.env.KES_CHILD_PROCESS ? getIPCLogger : getLogger
69
+
70
+ // used by main.js to instantiate the loggers
71
+ module.exports.Log = Log
@@ -0,0 +1,24 @@
1
+ const bcrypt = require('bcrypt')
2
+
3
+ function hash (myPlaintextPassword, saltRounds) {
4
+ return new Promise(function (resolve, reject) {
5
+ bcrypt.hash(myPlaintextPassword, saltRounds, function (err, hash) {
6
+ if (err) { return reject(err) }
7
+ return resolve(hash)
8
+ })
9
+ })
10
+ }
11
+
12
+ function compare (data, hash) {
13
+ return new Promise(function (resolve, reject) {
14
+ bcrypt.compare(data, hash, function (err, matched) {
15
+ if (err) { return reject(err) }
16
+ return resolve(matched)
17
+ })
18
+ })
19
+ }
20
+
21
+ module.exports = {
22
+ hash,
23
+ compare,
24
+ }
@@ -0,0 +1,136 @@
1
+ const os = require('os')
2
+ const path = require('path')
3
+ const baseDir = path.resolve(path.dirname(require.main.filename), '..')
4
+
5
+ const env = {
6
+ NODE_ENV: process.env.NODE_ENV,
7
+ KES_CONSOLE_LEVEL: parseInt(process.env.KES_CONSOLE_LEVEL, 10),
8
+ KES_LOG_LEVEL: parseInt(process.env.KES_LOG_LEVEL, 10),
9
+ KES_PATH_ASSETS: path.join(baseDir, 'assets'),
10
+ KES_PATH_DATA: process.env.KES_PATH_DATA || getAppPath('Karaoke Eternal Server'),
11
+ KES_PATH_WEBROOT: path.join(baseDir, 'build'),
12
+ KES_PORT: parseInt(process.env.KES_PORT, 10) || 0,
13
+ KES_ROTATE_KEY: ['1', 'true'].includes(process.env.KES_ROTATE_KEY?.toLowerCase()),
14
+ KES_SCAN: ['1', 'true'].includes(process.env.KES_SCAN?.toLowerCase()),
15
+ KES_SCAN_CONSOLE_LEVEL: parseInt(process.env.KES_SCAN_CONSOLE_LEVEL, 10),
16
+ KES_SCAN_LOG_LEVEL: parseInt(process.env.KES_SCAN_LOG_LEVEL, 10),
17
+ KES_URL_PATH: process.env.KES_URL_PATH || '/',
18
+ // support PUID/PGID convention
19
+ KES_PUID: parseInt(process.env.PUID, 10),
20
+ KES_PGID: parseInt(process.env.PGID, 10),
21
+ }
22
+
23
+ const yargs = require('yargs')
24
+ .version(false) // disable default handler
25
+ .option('consoleLevel', {
26
+ describe: 'Web server console output level (default=4)',
27
+ number: true,
28
+ requiresArg: true,
29
+ })
30
+ .option('data', {
31
+ describe: 'Absolute path of folder for database files',
32
+ requiresArg: true,
33
+ type: 'string',
34
+ })
35
+ .option('logLevel', {
36
+ describe: 'Web server log file level (default=3)',
37
+ number: true,
38
+ requiresArg: true,
39
+ })
40
+ .option('p', {
41
+ alias: 'port',
42
+ describe: 'Web server port (default=0/auto)',
43
+ number: true,
44
+ requiresArg: true,
45
+ })
46
+ .option('rotateKey', {
47
+ describe: 'Rotate the session key at startup',
48
+ })
49
+ .option('scan', {
50
+ describe: 'Run the media scanner at startup',
51
+ })
52
+ .option('scanConsoleLevel', {
53
+ describe: 'Media scanner console output level (default=4)',
54
+ number: true,
55
+ requiresArg: true,
56
+ })
57
+ .option('scanLogLevel', {
58
+ describe: 'Media scanner log file level (default=3)',
59
+ number: true,
60
+ requiresArg: true,
61
+ })
62
+ .option('urlPath', {
63
+ describe: 'Web server URL base path (default=/)',
64
+ requiresArg: true,
65
+ type: 'string',
66
+ })
67
+ .option('v', {
68
+ alias: 'version',
69
+ describe: 'Output the Karaoke Eternal Server version and exit',
70
+ })
71
+ .usage('$0')
72
+ .usage(' Logging options use the following numeric levels:')
73
+ .usage(' 0=off, 1=error, 2=warn, 3=info, 4=verbose, 5=debug')
74
+
75
+ let argv = yargs.argv
76
+
77
+ let _app
78
+ if (process.versions.electron) {
79
+ _app = require('electron').app
80
+
81
+ // see https://github.com/yargs/yargs/blob/master/docs/api.md#argv
82
+ if (_app.isPackaged) {
83
+ argv = yargs.parse(process.argv.slice(1))
84
+ }
85
+ }
86
+
87
+ if (argv.version) {
88
+ console.log(_app ? _app.getVersion() : process.env.npm_package_version)
89
+ process.exit(0)
90
+ }
91
+
92
+ // CLI options take precendence over env vars
93
+ if (argv.scan) {
94
+ env.KES_SCAN = true
95
+ }
96
+
97
+ if (argv.rotateKey) {
98
+ env.KES_ROTATE_KEY = true
99
+ }
100
+
101
+ const opts = {
102
+ data: 'KES_PATH_DATA',
103
+ port: 'KES_PORT',
104
+ scanConsoleLevel: 'KES_SCAN_CONSOLE_LEVEL',
105
+ scanLogLevel: 'KES_SCAN_LOG_LEVEL',
106
+ serverConsoleLevel: 'KES_CONSOLE_LEVEL',
107
+ serverLogLevel: 'KES_LOG_LEVEL',
108
+ urlPath: 'KES_URL_PATH',
109
+ }
110
+
111
+ for (const opt in opts) {
112
+ if (typeof argv[opt] !== 'undefined') {
113
+ env[opts[opt]] = argv[opt]
114
+ process.env[opts[opt]] = argv[opt]
115
+ }
116
+ }
117
+
118
+ module.exports = env
119
+
120
+ function getAppPath (appName) {
121
+ const home = os.homedir ? os.homedir() : process.env.HOME
122
+
123
+ switch (process.platform) {
124
+ case 'darwin': {
125
+ return path.join(home, 'Library', 'Application Support', appName)
126
+ }
127
+
128
+ case 'win32': {
129
+ return process.env.APPDATA || path.join(home, 'AppData', 'Roaming', appName)
130
+ }
131
+
132
+ default: {
133
+ return process.env.XDG_CONFIG_HOME || path.join(home, '.config', appName)
134
+ }
135
+ }
136
+ }
@@ -0,0 +1,81 @@
1
+ const { app, shell, clipboard, dialog, BrowserWindow, Tray, Menu } = require('electron')
2
+ const path = require('path')
3
+ const log = require('./Log')(`main:electron[${process.pid}]`)
4
+
5
+ // Keep a global reference of the window object, if you don't, the window will
6
+ // be closed automatically when the JavaScript object is garbage collected.
7
+ let win
8
+ let tray = null
9
+ const status = {
10
+ url: '',
11
+ }
12
+
13
+ module.exports = ({ env }) => {
14
+ // event handlers
15
+ app.on('ready', createWindow)
16
+ app.on('quit', (e, code) => { log.info(`exiting (${code})`) })
17
+
18
+ function createWindow () {
19
+ win = new BrowserWindow({
20
+ show: false,
21
+ skipTaskbar: true, // windows
22
+ })
23
+
24
+ // macOS
25
+ if (app.dock) {
26
+ app.dock.hide()
27
+ }
28
+
29
+ if (process.platform === 'win32') {
30
+ // white 32x32
31
+ tray = new Tray(path.join(env.KES_PATH_ASSETS, 'mic-white@2x.png'))
32
+ } else {
33
+ // blackish 32x32 (template works in light and dark macOS modes)
34
+ tray = new Tray(path.join(env.KES_PATH_ASSETS, 'mic-blackTemplate.png'))
35
+ tray.setPressedImage(path.join(env.KES_PATH_ASSETS, 'mic-white.png'))
36
+ }
37
+
38
+ tray.setToolTip('Karaoke Eternal Server v' + app.getVersion())
39
+ tray.on('double-click', launchBrowser)
40
+ updateMenu()
41
+
42
+ // Emitted when the window is closed.
43
+ win.on('closed', () => {
44
+ win = null
45
+ tray = null
46
+ })
47
+ }
48
+
49
+ function launchBrowser () {
50
+ if (status.url) {
51
+ shell.openExternal(status.url)
52
+ }
53
+ }
54
+
55
+ function setError (msg) {
56
+ dialog.showErrorBox('Karaoke Eternal Server', `Error: ${msg}`)
57
+ }
58
+
59
+ function setStatus (key, val) {
60
+ status[key] = val
61
+ updateMenu()
62
+ }
63
+
64
+ function updateMenu () {
65
+ if (!tray) return
66
+
67
+ const menu = [
68
+ { label: 'Karaoke Eternal Server v' + app.getVersion(), enabled: false },
69
+ { label: status.url, enabled: false },
70
+ { type: 'separator' },
71
+ { label: 'Open in browser', click: launchBrowser },
72
+ { label: 'Copy URL', click: () => clipboard.writeText(status.url) },
73
+ { type: 'separator' },
74
+ { label: 'Quit Karaoke Eternal Server', role: 'quit' },
75
+ ]
76
+
77
+ tray.setContextMenu(Menu.buildFromTemplate(menu))
78
+ }
79
+
80
+ return { app, setStatus, setError }
81
+ }
@@ -0,0 +1,20 @@
1
+ const { promisify } = require('util')
2
+ const fs = require('fs')
3
+ const stat = promisify(fs.stat)
4
+ const getPerms = require('./getPermutations')
5
+
6
+ module.exports = async function getCdgName (file) {
7
+ // upper and lowercase permutations since fs may be case-sensitive
8
+ for (const ext of getPerms('cdg')) {
9
+ const cdg = file.substring(0, file.lastIndexOf('.') + 1) + ext
10
+
11
+ try {
12
+ await stat(cdg)
13
+ return cdg
14
+ } catch (err) {
15
+ // try another permutation
16
+ }
17
+ } // end for
18
+
19
+ return false
20
+ }
@@ -0,0 +1,51 @@
1
+ /*
2
+ Copyright © 2016 Andrew Powell
3
+
4
+ This Source Code Form is subject to the terms of the Mozilla Public
5
+ License, v. 2.0. If a copy of the MPL was not distributed with this
6
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
+
8
+ The above copyright notice and this permission notice shall be
9
+ included in all copies or substantial portions of this Source Code Form.
10
+ */
11
+ const webpackDevMiddleware = require('webpack-dev-middleware')
12
+
13
+ module.exports = (compiler, opts) => {
14
+ const middleware = webpackDevMiddleware(compiler, opts)
15
+
16
+ return (ctx, next) => {
17
+ // wait for webpack-dev-middleware to signal that the build is ready
18
+ const ready = new Promise((resolve, reject) => {
19
+ for (const comp of [].concat(compiler.compilers || compiler)) {
20
+ comp.hooks.failed.tap('KoaWebpack', (error) => {
21
+ reject(error)
22
+ })
23
+ }
24
+
25
+ middleware.waitUntilValid(() => {
26
+ resolve(true)
27
+ })
28
+ })
29
+
30
+ // tell webpack-dev-middleware to handle the request
31
+ const init = new Promise((resolve) => {
32
+ // call express-style middleware
33
+ middleware(
34
+ ctx.req,
35
+ {
36
+ end: (content) => {
37
+ // eslint-disable-next-line no-param-reassign
38
+ ctx.body = content
39
+ resolve()
40
+ },
41
+ getHeader: ctx.get.bind(ctx),
42
+ setHeader: ctx.set.bind(ctx),
43
+ locals: ctx.state
44
+ },
45
+ () => resolve(next())
46
+ )
47
+ })
48
+
49
+ return Promise.all([ready, init])
50
+ }
51
+ }
@@ -0,0 +1,10 @@
1
+ const path = require('path')
2
+ const fs = require('fs')
3
+ const { promisify } = require('util')
4
+ const readdir = promisify(fs.readdir)
5
+
6
+ const getFolders = dir => readdir(dir, { withFileTypes: true })
7
+ .then(list => Promise.all(list.map(ent => ent.isDirectory() ? path.resolve(dir, ent.name) : null)))
8
+ .then(list => list.filter(f => !!f).sort())
9
+
10
+ module.exports = getFolders
@@ -0,0 +1,27 @@
1
+ // based on https://github.com/tnnevol/webpack-hot-middleware-for-koa2
2
+
3
+ const webpackHotMiddleware = require('webpack-hot-middleware')
4
+
5
+ module.exports = (compiler, opts) => {
6
+ const middleware = webpackHotMiddleware(compiler, opts)
7
+
8
+ return async (ctx, next) => {
9
+ const { end: originalEnd } = ctx.res
10
+
11
+ const runNext = await new Promise(resolve => {
12
+ ctx.res.end = function () {
13
+ originalEnd.apply(this, arguments)
14
+ resolve(false)
15
+ }
16
+
17
+ // call express-style middleware
18
+ middleware(ctx.req, ctx.res, () => {
19
+ resolve(true)
20
+ })
21
+ })
22
+
23
+ if (runNext) {
24
+ await next()
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,16 @@
1
+ // https://gist.github.com/savokiss/96de34d4ca2d37cbb8e0799798c4c2d3
2
+ module.exports = function () {
3
+ const interfaces = require('os').networkInterfaces()
4
+
5
+ for (const devName in interfaces) {
6
+ const iface = interfaces[devName]
7
+
8
+ for (let i = 0; i < iface.length; i++) {
9
+ const alias = iface[i]
10
+
11
+ if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
12
+ return alias.address
13
+ }
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,21 @@
1
+ // return all uppercase and lowercase permutations of str
2
+ // based on https://stackoverflow.com/a/27995370
3
+ function getPermutations (str) {
4
+ const results = []
5
+ const arr = str.split('')
6
+ const len = Math.pow(arr.length, 2)
7
+
8
+ for (let i = 0; i < len; i++) {
9
+ for (let k = 0, j = i; k < arr.length; k++, j >>= 1) {
10
+ arr[k] = (j & 1) ? arr[k].toUpperCase() : arr[k].toLowerCase()
11
+ }
12
+
13
+ const combo = arr.join('')
14
+ results.push(combo)
15
+ }
16
+
17
+ // remove duplicates
18
+ return results.filter((ext, pos, self) => self.indexOf(ext) === pos)
19
+ }
20
+
21
+ module.exports = getPermutations
@@ -0,0 +1,30 @@
1
+ const childProcess = require('child_process')
2
+ const command = 'wmic logicaldisk get Caption, ProviderName'
3
+ // sample output:
4
+ //
5
+ // Caption ProviderName
6
+ // C:
7
+ // D:
8
+ // E: \\vboxsrv\Downloads
9
+ // F: \\vboxsrv\Karaoke
10
+
11
+ module.exports = function () {
12
+ return new Promise((resolve, reject) => {
13
+ childProcess.exec(command, (err, stdout) => {
14
+ if (err) {
15
+ return reject(err)
16
+ }
17
+
18
+ // split to lines
19
+ const rows = stdout.split(/\r?\n/)
20
+
21
+ // first line is heading(s)
22
+ rows.shift()
23
+
24
+ resolve(rows.filter(r => !!r.trim()).map(r => ({
25
+ path: r.trim().substring(0, 2) + '\\',
26
+ label: r,
27
+ })))
28
+ })
29
+ })
30
+ }
@@ -0,0 +1,12 @@
1
+ // cookie helper based on
2
+ // http://stackoverflow.com/questions/3393854/get-and-set-a-single-cookie-with-node-js-http-server
3
+ module.exports = function parseCookie (cookie) {
4
+ const list = {}
5
+
6
+ cookie && cookie.split(';').forEach(c => {
7
+ const parts = c.split('=')
8
+ list[parts.shift().trim()] = decodeURI(parts.join('='))
9
+ })
10
+
11
+ return list
12
+ }
@@ -0,0 +1,29 @@
1
+ const Library = require('../Library')
2
+ const Queue = require('../Queue')
3
+ const Rooms = require('../Rooms')
4
+ const {
5
+ LIBRARY_PUSH,
6
+ QUEUE_PUSH,
7
+ } = require('../../shared/actionTypes')
8
+
9
+ async function pushQueuesAndLibrary (io) {
10
+ // emit (potentially) updated queues to each room
11
+ // it's important that this happens before the library is pushed,
12
+ // otherwise queue items might reference newly non-existent songs
13
+ for (const { room, roomId } of Rooms.getActive(io)) {
14
+ io.to(room).emit('action', {
15
+ type: QUEUE_PUSH,
16
+ payload: await Queue.get(roomId),
17
+ })
18
+ }
19
+
20
+ // invalidate cache
21
+ Library.cache.version = null
22
+
23
+ io.emit('action', {
24
+ type: LIBRARY_PUSH,
25
+ payload: await Library.get(),
26
+ })
27
+ }
28
+
29
+ module.exports = pushQueuesAndLibrary