123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- #!/usr/bin/env node
- // omg I am tired of code
- import {getPlayer} from './players.js'
- import {parseOptions} from './general-util.js'
- import {getItemPathString} from './playlist-utils.js'
- import Backend from './backend.js'
- import setupClient from './client.js'
- import TelnetServer from './telnet.js'
- import {CommandLineInterface} from 'tui-lib/util/interfaces'
- import * as ansi from 'tui-lib/util/ansi'
- import {readFile, writeFile} from 'node:fs/promises'
- import os from 'node:os'
- import path from 'node:path'
- // Hack to get around errors when piping many things to stdout/err
- // (from general-util promisifyProcess)
- process.stdout.setMaxListeners(Infinity)
- process.stderr.setMaxListeners(Infinity)
- process.on('unhandledRejection', error => {
- console.error(ansi.setForeground(ansi.C_RED) + "** There was an uncatched error! **" + ansi.resetAttributes())
- console.error("Don't worry, your music files are all okay.")
- console.error("This just means there was a bug in mtui.")
- console.error("In order to verify that the program won't run weirdly, it has stopped.")
- console.error(ansi.setForeground(ansi.C_RED) + "Error stack:" + ansi.resetAttributes())
- console.error(error.stack)
- console.error(ansi.setForeground(ansi.C_RED) + "Error object:" + ansi.resetAttributes())
- console.error(error)
- console.error("(End of error log.)")
- process.stdout.write(ansi.cleanCursor())
- process.exit(1)
- })
- async function main() {
- const playlistSources = []
- const options = await parseOptions(process.argv.slice(2), {
- 'player': {
- type: 'value',
- async validate(playerName) {
- if (await getPlayer(playerName)) {
- return true
- } else {
- return 'a known player identifier'
- }
- }
- },
- 'player-options': {type: 'series'},
- 'stress-test': {type: 'flag'},
- 'telnet-server': {type: 'flag'},
- 'skip-config-file': {type: 'flag'},
- 'config-file': {type: 'value'},
- [parseOptions.handleDashless](option) {
- playlistSources.push(option)
- },
- })
- if (options['player-options'] && !options['player']) {
- console.error('--player must be specified in order to use --player-options')
- process.exit(1)
- }
- let jsonConfig = {}
- let jsonError = null
- const jsonPath =
- (options['config-file']
- ? path.resolve(options['config-file'])
- : path.join(os.homedir(), '.mtui', 'config.json'))
- try {
- jsonConfig = JSON.parse(await readFile(jsonPath))
- } catch (error) {
- if (error.code !== 'ENOENT') {
- jsonError = error
- }
- }
- if (jsonError) {
- console.error(`Error loading JSON config:`)
- console.error(jsonError.message)
- console.error(`Edit the file below to fix the error, or run mtui with --skip-config-file.`)
- console.error(jsonPath)
- process.exit(1)
- }
- const backend = new Backend({
- playerName: options['player'],
- playerOptions: options['player-options']
- })
- const result = await backend.setup()
- if (result.error) {
- console.error(result.error)
- process.exit(1)
- }
- backend.on('playing', track => {
- if (track) {
- writeFile(backend.rootDirectory + '/current-track.txt',
- getItemPathString(track))
- writeFile(backend.rootDirectory + '/current-track.json',
- JSON.stringify(track, null, 2))
- }
- })
- const { appElement, dirtyTerminal, flushable, root } = await setupClient({
- backend,
- screenInterface: new CommandLineInterface(),
- writable: process.stdout
- })
- appElement.on('quitRequested', () => {
- if (telnetServer) {
- telnetServer.disconnectAllSockets('User closed mtui - see you!')
- }
- process.exit(0)
- })
- appElement.on('suspendRequested', () => {
- process.kill(process.pid, 'SIGTSTP')
- })
- process.on('SIGCONT', () => {
- flushable.resizeScreen({lines: flushable.screenLines, cols: flushable.screenCols})
- process.stdin.setRawMode(false)
- process.stdin.setRawMode(true)
- dirtyTerminal()
- root.renderNow()
- })
- if (playlistSources.length === 0) {
- if (jsonConfig.defaultPlaylists) {
- playlistSources.push(...jsonConfig.defaultPlaylists)
- } else {
- playlistSources.push({
- name: 'My ~/Music Library',
- comment: (
- '(Add tracks and folders to ~/Music to make them show up here,' +
- ' or pass mtui your own playlist.json file!)'),
- source: ['crawl-local', os.homedir() + '/Music']
- })
- }
- }
- const loadPlaylists = async () => {
- for (const source of playlistSources) {
- await appElement.loadPlaylistOrSource(source, true)
- }
- }
- const loadPlaylistPromise = loadPlaylists()
- let telnetServer
- if (options['telnet-server']) {
- telnetServer = new TelnetServer(backend)
- await telnetServer.listen(1244)
- appElement.attachAsServerHost(telnetServer)
- }
- if (options['stress-test']) {
- await loadPlaylistPromise
- const w = 80
- const h = 40
- flushable.resizeScreen({lines: w, cols: h})
- root.w = w
- root.h = h
- root.fixAllLayout()
- /* eslint-disable-next-line no-unused-vars */
- const XXstress = func => '[disabled]'
- const stress = func => {
- const start = Date.now()
- let n = 0
- while (Date.now() < start + 1000) {
- func()
- n++
- }
- return n
- }
- const nRenderAndFlush = stress(() => {
- root.renderTo(flushable)
- flushable.flush()
- })
- const nFixAllLayout = stress(() => {
- root.fixAllLayout()
- })
- const listings = appElement.tabber.tabberElements
- const lastListing = listings[listings.length - 1]
- const nBuildItems = stress(() => {
- lastListing.buildItems()
- })
- process.stdout.write(ansi.cleanCursor() + ansi.clearScreen() + '\n')
- console.log('# of times we can render & flush:', nRenderAndFlush)
- console.log('# of times we can fix all layout:', nFixAllLayout)
- console.log('# of times we can build items:', nBuildItems)
- process.exit(0)
- return
- }
- }
- main().catch(err => {
- console.error(err)
- process.exit(1)
- })
|