"use strict"; const config = require('./config.json') const library = require('./jukebox/library'); const ScriptRunner = require('./jukebox/scripts')(config); const express = require('express'); const morgan = require('morgan'); const handlebars = require('express-handlebars'); const multer = require('multer'); const uuid = require('uuid/v4'); var storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, config.media_path) }, filename: function (req, file, cb) { var track_uuid = uuid(); cb(null, track_uuid) } }); var upload = multer({ storage: storage, }); const { createLogger, format, transports } = require('winston'); const { throttle } = require('throttle-debounce'); var logger = createLogger({ transports: [ new transports.Console() ] }); logger.level = config.debug ? 'debug' : 'info'; const MediaPlayer = require('./jukebox/media-player')(config, logger); const TagReader = require('./jukebox/tag-reader')(config, logger); const play_log = require('./jukebox/library/play-log'); const sqlite3 = require('sqlite3'); var db = new sqlite3.Database(config.db); const MediaLibrarySqliteBackend = require('./jukebox/library/sqlite-backend')(config, db); const MediaLibrary = new library.Library(config, MediaLibrarySqliteBackend); const PlayLog = new play_log.PlayLog(); var app = express(); var server = require('http').createServer(); const WebSocket = require('ws'); const WebSocketServer = WebSocket.Server; var wss = new WebSocketServer({server: server}); function exitHandler(options, err) { logger.debug('closing child tasks'); if (err) logger.debug(err.stack); // FIXME When do we actually need to call this? Adding these seems // to have fixed a problem with orphaned children, but not we get // two sets of "closing child tasks" messages when we shut down. TagReader.kill(); MediaPlayer.kill(); if (options.exit) { process.exit(); } }; process.on('exit', exitHandler.bind(null)); process.on('SIGINT', exitHandler.bind(null, {exit:true})); process.on('SIGUSR2', exitHandler.bind(null, {exit:true})); const throttledTag = throttle(config.global_throttle, true, tag => { ScriptRunner.find(tag).then((fulfilled) => { if (false === fulfilled) { MediaLibrary.find(tag) } }).catch(err => { logger.error("got an error handling the tag: " + err); }); }); TagReader.on('message', tag => { if (tag == 'E1' || tag == 'E2') { logger.debug("tagreader had rfid read error", { tag: tag }); return; } logger.debug("got tag from tagreader", { tag: tag }); throttledTag(tag); }); if (config.debug) { MediaPlayer.on('message', line => { logger.debug("mediaplayer sent message", { line: line }); }); } MediaPlayer.on('command', tag => { PlayLog.updateLog(tag); }); var hbs = handlebars.create({ extname: ".hbs", }); MediaLibrary.on('action', MediaPlayer.handleTag.bind(MediaPlayer)); app.engine(".hbs", hbs.engine); app.set('view engine', '.hbs'); app.set('views', __dirname + '/views'); app.use(morgan('dev')) app.use(express.json()) app.use(express.static(__dirname + '/static')) PlayLog.on('update', tag => { app.render("log", { layout: false, play_log: PlayLog.getLog(), }, (err, html) => { var data = { html: html, tag: tag.tag }; wss.broadcast(JSON.stringify(data)); }); }); app.get('/', function(req, res, next) { res.render('index', { last_tag: PlayLog.getLastTag(), config: config, play_log: PlayLog.getLog(), }); }); app.get('/api/tags', function(req, res, next) { db.all("SELECT tags.tag, count(library.tag) tag_count FROM (SELECT DISTINCT tag FROM tags) tags LEFT JOIN library ON tags.tag = library.tag GROUP BY tags.tag", (err, rows) => { res.send(rows); }); }); app.patch('/api/tracks/:track', function(req, res, next) { var track_uuid = req.params.track, label = req.body.label, tag = req.body.tag; db.run("UPDATE library SET label = ?, tag = ? WHERE uuid = ?", label, tag, track_uuid, err => { db.get("SELECT * FROM library WHERE uuid = ?", track_uuid, (err, row) => { res.send(row); }); }); }); app.get('/api/tracks', function(req, res, next) { db.all("SELECT * FROM library ORDER BY label ASC", (err, rows) => { res.send(rows); }); }); app.post('/api/tracks', upload.single('track'), function(req, res, next) { db.run("INSERT INTO library (label, uuid) VALUES (?, ?)", req.file.originalname, req.file.filename, err => { res.redirect('/library'); }); }); app.get('/library', function(req, res, next) { res.render('library', { title: 'Library', }); }); wss.broadcast = function broadcast(data) { wss.clients.forEach(function each(client) { if (client.readyState === WebSocket.OPEN) { client.send(data); } }); }; wss.on('connection', function(ws) { logger.info('websocket client connected'); ws.on('close', function() { logger.info('websocket client disconnected'); }); }); server.on('request', app); server.listen(config.port, function() { logger.info('express listening on http://localhost:' + config.port) }) // vim:ts=2 sw=2 et: