diff --git a/README.md b/README.md index b6dcdc7..d506c6a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -mStream is an mp3 streaming server. It's focus is on ease of installation +mStream is an mp3 streaming server. It's focus is on ease of installation. mStream will work right out of the box without any configuration + ## Installation @@ -6,15 +7,24 @@ Run the following commands: ```shell npm install -g mstream -npm link mstream mstream musicDirectory/ ``` -Make sure it's working by check checking out http://localhost:3000/ +Make sure it's working by checking out http://localhost:3000/ -## Download Playlists -mStream now supports zipped playlist downloading without any configuration. When you click the download button, a zipped directory of all the songs on the current playlist will be downloaded to your machine. +## Options + +```shell +-p, --port -> set port number +-l, --login -> enable user login +-u, --user -> add user +-x, --password -> set Password +-d, --database -> set the database file +-t, --tunnel -> tunnel +-g, --gateway -> set gateway for tunnelling +``` + ## Design @@ -23,16 +33,6 @@ mStream features a responsive frontend that works on everything from desktops to ![Looking Good!](public/img/mstream-current.png) -## Options -```shell --p, --port -> set port number --t, --tunnel -> tunnel --g, --gateway -> set gateway for tunnelling --l, --login -> enable user login --u, --user -> add user --x, --password -> set Password -``` - ## User System The current user system is a simple as it comes. There is just one user that can be set via the command line. @@ -43,7 +43,7 @@ mstream music/ -l -u [username] -x [password] ``` -The user system is simple for a few reasons. First, I wanted to have a user system that doesn't need database to work. Secondly, mStream is a personnal server and most users don't need anything more complex than this. +The user system is simple for a few reasons. First, I wanted to have a user system that doesn't need a database to work. Secondly, mStream is a personnal server and most users don't need anything more complex than this. Future versions of this login system will allower for multiple users and user permssions, such as limitting users from saving playlists. @@ -64,19 +64,50 @@ mstream [directory] -t -g [gatewayIP] mstream musicDirectory/ -t -g 192.168.1.1 ``` +Please note that not all routers will alow this. Some routers may close this port after a period of time. + + ## Database -mStream currently uses a SQLite database to a music library. Use the /db/recursive-scan call to create the library. +mStream currently uses a SQLite database for a music library. You have the option of using a beets DB or having a mstream create it's own DB. -The databases functions are still being fine tuned and may be changed in the future. +#### Beets DB +http://beets.io/ + +mStream can use your beets database without any configuration. +```shell +mstream musicDirectory/ -d path/to/beets.db +``` + +Currently using beets is the reccomended way to create a music database. + +#### The Bad News + +Currently there's not many libraries for scraping music information for node and most of them are unmaintaned. The one I'm currently using is slow, but is being updated regularly. However it will grind the service to a halt if you try to parse a large library. + +If you're still interested in using mStream to build your DB, use the /db/recursive-scan call to do this. Don't be surprised if you can't access your server while this is going on. + +I will be experimenting with some other libraries in the near future. In the meantine, I suggest you use beets for all your music DB needs. + +#### More bad news +Node v6 currently does not play nice with the sqlite3 library. You need to use Node v5 or earlier for the DB to work. + +The sqlite3 library is activetly mainted so this should be fixed soon -WARNING: using the /db/recursive-scan call is currently unreliable and can cause the app to crash. The solution is to either move away from SQLite or to use a seperate script to create the database. For now you're stuck with it as the only way to create the db + +## Download Playlists + +mStream now supports zipped playlist downloading without any configuration. When you click the download button, a zipped directory of all the songs on the current playlist will be downloaded to your machine. -## API Calls +## API + +mStream uses a JSON based API for all calls. + +API Calls * POST: /dirparser - Get list of files and folders for a given directory * PARAM: dir - directory to scan * PARAM: filetypes - JSON array of filetypes to return @@ -92,9 +123,6 @@ WARNING: using the /db/recursive-scan call is currently unreliable and can cause * POST: /download * PARAM: fileArray - JSON array of files to download * RETURN: Zipped directory of files -* GET: /db/recursive-scan - Scans all files and adds metadata to the DB - * WARNING: This is an expensive operation and will make using webapp slow - * RETURN: Message of successful kickoff * POST: /db/search * PARAM: search - sring to search for * RETURN: JSON array of artists and albums that match search @@ -108,14 +136,19 @@ WARNING: using the /db/recursive-scan call is currently unreliable and can cause * POST: /db/album-songs - Find all songs for a given album * PARAM: album - name of album * RETURN: JSON array of all songs +* GET: /db/recursive-scan - Scans all files and adds metadata to the DB + * WARNING: This is an expensive operation and will make using webapp slow + * RETURN: Message of successful kickoff * GET: /db/status * WIP + + ## TODO - GET request to jump to playlist or directory - Look into taglib for id3 info -- Add support for MySQL DB +- Recursive Directory Downloading - SSL support -- Fix column header position +- Save scroll position diff --git a/mstream.js b/mstream.js index a82065b..abb7ee4 100755 --- a/mstream.js +++ b/mstream.js @@ -1,4 +1,5 @@ #!/usr/bin/env node +"use strict"; var express = require('express'); var app = express(); @@ -10,17 +11,6 @@ var archiver = require('archiver'); // Zip Compression var os = require('os'); - -// For DB -var sqlite3 = require('sqlite3').verbose(); -var db = new sqlite3.Database('mstreamdb.lite'); -var metadata = require('musicmetadata'); // TODO: Look into replacng with taglib -var scanLock = false; -var fileCount = 0; -var fileCounter = 0; -var arrayOfSongs = []; - - // Setup Command Line Interface program .version('1.9.0') @@ -30,19 +20,30 @@ program .option('-l, --login', 'Require users to login') .option('-u, --user ', 'Set Username') .option('-x, --password ', 'Set Password') -// .option('-d, --database') -// .option('-s, --ssl', 'Setup SSL') - .option('-k, --key ', 'Add SSL Key') - .option('-c, --cert ', 'Add SSL Certificate') -// .option('-d, --db ', 'Add SSL Certificate', mstreamdb.lite) + // .option('-k, --key ', 'Add SSL Key') + // .option('-c, --cert ', 'Add SSL Certificate') + .option('-d, --database ', 'Add SSL Certificate', 'mstreamdb.lite') .parse(process.argv); +// TODO: Cleanup global vars +// For DB +var sqlite3 = require('sqlite3').verbose(); +var db = new sqlite3.Database(program.database); +var metadata = require('musicmetadata'); // TODO: Look into replacng with taglib +var scanLock = false; +var fileCount = 0; +var fileCounter = 0; +var arrayOfSongs = []; + + // Get starting directory from command line arguments if(process.argv[2]){ var startdir = process.argv[2]; + + // Abort if supplied value is not a directory if(!fs.statSync(startdir ).isDirectory()){ console.log('Could not find directory. Aborting.'); process.exit(1); @@ -53,19 +54,17 @@ if(process.argv[2]){ process.exit(1); } -// Make sure the user supplied a real directory -if(!fs.statSync(startdir).isDirectory()){ - console.log('Could not find the supplied directory'); - console.log('Please use the following format: mstream musicDirectory/'); - process.exit(1); -} // Add the slash at the end if it's not already there if(startdir.slice(-1) !== '/'){ startdir += '/'; } + + // Normalize for all OS startdir = fe.normalize(startdir); +var rootDir = process.cwd() + startdir; + // Static files app.use( express.static(__dirname + '/public')); @@ -75,17 +74,19 @@ app.use( '/' , express.static( process.cwd() + '/' + startdir)); app.use(bodyParser.json()); // support json encoded bodies app.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies + // Handle ports. Default is 3000 var port = program.port; - console.log('Access mStream locally: http://localhost:' + port); // Auto tunnel to the external world -try{ - if(program.tunnel){ +if(program.tunnel){ + try{ + var natpmp = require('nat-pmp'); + // Use the user supplied Gateway IP or try to find it manually if(program.gateway){ var gateway = program.gateway; }else{ @@ -107,24 +108,23 @@ try{ getIP(function (err, ip) { if (err) { - // every service in the list has failed + // every service in the list has failed throw err; } console.log('Access mStream on the internet: http://' + ip + ':' + port); }); } + catch (e) { + console.log('WARNING: mStream tunnel functionality has failed. This feature is still experimental'); + console.log(e); + } } -catch (e) { - console.log('WARNING: mStream tunnel functionality has failed. This feature is still experimental'); - console.log(e); -} + // TODO: Print the local network IP -////////////////////////////////////////////////// -////////////////////////////////////////////////// -////////////////////////////////////////////////// +// Login functionality if(program.login){ if(!program.password || !program.user){ console.log('User credentials are missing. Please make sure to supply both a username and password via the -u and -p commands respectivly. Aborting'); @@ -145,7 +145,7 @@ if(program.login){ // Encrypt the password bcrypt.genSalt(10, function(err, salt) { bcrypt.hash(program.password, salt, function(err, hash) { - // Store hash in your password DB. + // Store hash in your password DB. Users[program.user]['password'] = hash; }); }); @@ -181,7 +181,7 @@ if(program.login){ successRedirect : '/', // redirect to the secure profile section failureRedirect : '/login', // redirect back to the signup page if there is an error //failureFlash : true // allow flash messages - //}, 300); + //}, 300); })); @@ -206,7 +206,7 @@ if(program.login){ passReqToCallback : true // allows us to pass back the entire request to the callback }, function(req, username, password, done) { // callback with email and password from our form - // TODO: Handle empty username + // TODO: Handle empty username // Check is user is in array if(typeof Users[username] === 'undefined') { @@ -243,17 +243,15 @@ if(program.login){ // Enable middleware app.use(authenticateUser); - // TODO: Authenticat all HTTP requests for music files (mp3 and other formats) + // TODO: Authenticate all HTTP requests for music files (mp3 and other formats) } - - // Serve the webapp app.get('/', function (req, res) { - res.sendFile('public/mstream.html', { root: __dirname }); + res.sendFile('public/mstream.html', { root: __dirname }); }); @@ -266,6 +264,8 @@ app.post('/dirparser', function (req, res) { var path = req.body.dir; var fileTypesArray = JSON.parse(req.body.filetypes); + // TODO: Use a default value if user doesn't supply this + // Will only show these files. Prevents people from snooping around // TODO: Move to global vairable @@ -293,34 +293,23 @@ app.post('/dirparser', function (req, res) { // Handle Directories - if(stat.isDirectory()){ + if(stat.isDirectory()){ tempDirArray["type"] = 'directory'; tempDirArray["name"] = files[i]; directories.push(tempDirArray); }else{ // Handle Files - // Make list of mp3 files - // if(files[i].substr(files[i].length - 3) === 'mp3'){ - // tempFileArray["type"] = 'mp3'; - // tempFileArray["name"] = files[i]; - // filesArray.push(tempFileArray); - // } - - // Get the last three letters of files + // Get the file extension var extension = getFileType(files[i]); - // var extension = files[i].substr(files[i].length - 3); - // // compensate for flac - // if(extension === 'lac'){ - // extension = 'flac'; - // } + if (fileTypesArray.indexOf(extension) > -1 && masterFileTypesArray.indexOf(extension) > -1) { tempFileArray["type"] = extension; tempFileArray["name"] = files[i]; filesArray.push(tempFileArray); - } + } } } @@ -339,9 +328,10 @@ app.post('/dirparser', function (req, res) { function getFileType(filename){ var extension = filename.substr(filename.length - 3); + // compensate for flac if(extension === 'lac'){ - extension = 'flac'; + extension = 'flac'; } return extension; @@ -370,15 +360,18 @@ app.post('/saveplaylist', function (req, res){ fs.writeFile('.mstream-playlists/' + title + '.m3u', writeString, function (err) { if (err) throw err; + console.log('It\'s saved!'); res.send(); }); }); + app.get('/getallplaylists', function (req, res){ var files = fs.readdirSync('.mstream-playlists/'); var playlists = []; + // // loop through files for (var i = 0; i < files.length; i++) { if(files[i].substr(files[i].length - 3) === 'm3u'){ @@ -389,11 +382,12 @@ app.get('/getallplaylists', function (req, res){ res.send(JSON.stringify(playlists)); }); + // Find all playlists app.get('/loadplaylist', function (req, res){ // TODO: Scrub user input - var playlist = req.query.filename; + var contents = fs.readFileSync('.mstream-playlists/' + playlist, 'utf8'); var contents = contents.split(os.EOL); @@ -425,11 +419,11 @@ app.post('/download', function (req, res){ archive.on('error', function(err) { console.log(err.message); - res.status(500).send({error: err.message}); + res.status(500).send('{error: err.message}'); }); archive.on('end', function() { - // TODO: add logging + // TODO: add logging console.log('Archive wrote %d bytes', archive.pointer()); }); @@ -469,397 +463,520 @@ app.post('/download', function (req, res){ // scan and screate database app.get('/db/recursive-scan', function(req,res){ - // Check if this is already running - if(scanLock === true){ - // Return error - res.status(401).send('{"error":"Scan in progress"}'); - return; + // Check if this is already running + if(scanLock === true){ + // Return error + res.status(401).send('{"error":"Scan in progress"}'); + return; + } + + try{ + // turn on scan lock + scanLock = true; + + // + fileCount = 0; + fileCounter = 0; + + // Make sure directory exits + var fileTypesArray = ["mp3", "flac", "wav", "ogg", "aac", "m4a"]; + + // Make sure it's a diectory + if(!fs.statSync( startdir).isDirectory()){ + // TODO: Write an error output + // 500 Output? + scanLock = false; + res.send(""); + return; } - try{ - // turn on scan lock - scanLock = true; - - // - fileCount = 0; - fileCounter = 0; - - // Make sure directory exits - var fileTypesArray = ["mp3", "flac", "wav", "ogg", "aac", "m4a"]; - - if(!fs.statSync( startdir).isDirectory()){ - // TODO: Write an error output - // 500 Output? - scanLock = false; - res.send(""); - return; - } - - countFiles(startdir, fileTypesArray); - console.log(fileCount); + countFiles(startdir, fileTypesArray); + console.log(fileCount); - - db.serialize(function() { - // These two queries will run sequentially. - db.run("drop table if exists files;"); - db.run("CREATE TABLE files ( id INTEGER PRIMARY KEY AUTOINCREMENT, title varchar DEFAULT NULL, artist varchar DEFAULT NULL, year int DEFAULT NULL, album varchar DEFAULT NULL, filename varchar, file_location text, filetype varchar);", function() { - // These queries will run in parallel and the second query will probably - // fail because the table might not exist yet. - recursiveScan(startdir, fileTypesArray); - - }); - }); - + db.serialize(function() { + // These two queries will run sequentially. + db.run("drop table if exists items;"); + db.run("CREATE TABLE items ( id INTEGER PRIMARY KEY AUTOINCREMENT, title varchar DEFAULT NULL, artist varchar DEFAULT NULL, year int DEFAULT NULL, album varchar DEFAULT NULL, path text, format varchar, track INTEGER);", function() { + // These queries will run in parallel and the second query will probably + // fail because the table might not exist yet. + console.log('TABLES CREATED'); + var emptypromise = emptyPromise(); + recursiveScanY(startdir, fileTypesArray, emptypromise); // TODO: Can we remove the fileTypesArray? + }); + }); - }catch(err){ - // Remove lock - scanLock = false; + }catch(err){ + // Remove lock + scanLock = false; - // Log error - res.status(500).send('{"error":"'+err+'"}'); - console.log(err); - return; - } + // Log error + res.status(500).send('{"error":"'+err+'"}'); + console.log(err); + return; + } - res.send("YA DID IT"); + res.send("YA DID IT"); }); -function recursiveScan(dir, fileTypesArray){ - var files = fs.readdirSync( dir ); - // loop through files - for (var i=0; i < files.length; i++) { - console.log(files[i]); - var filePath = dir + files[i]; - var stat = fs.statSync(filePath); - console.log(stat); - if(stat.isDirectory()){ - recursiveScan(filePath + '/', fileTypesArray); - }else{ - var extension = getFileType(files[i]); - // Make sure this is in our list of allowed files - if (fileTypesArray.indexOf(extension) > -1 ) { - parseFile(filePath, files[i], extension); - } - } +var arrayOfSongsToProcess = []; +function emptyPromise(){ + return new Promise(function(fulfill, reject) { + + if (true) { + fulfill(); + } else { + reject(); + } + }); +} + + +function recursiveScanY(dir, fileTypesArray, emptypromise){ + var files = fs.readdirSync( dir ); + + + // loop through files + for (var i=0; i < files.length; i++) { + var filePath = dir + files[i]; + var stat = fs.statSync(filePath); + + + if(stat.isDirectory()){ + recursiveScanY(filePath + '/', fileTypesArray, emptypromise); + }else{ + var extension = getFileType(files[i]); + + // Make sure this is in our list of allowed files + if (fileTypesArray.indexOf(extension) > -1 ) { + var songObject = { + filepath: filePath, + extension: extension + }; + arrayOfSongsToProcess.push(songObject); + + emptypromise.then(parseFileY, parseFileY); + } } + } } +// Pull song info from file +function parseFileY(){ + var thisSong = arrayOfSongsToProcess.pop(); + + // TODO: Test what happens when an error occurs + var parser = metadata(fs.createReadStream(thisSong.filepath), function (err, songInfo) { + fileCounter++; + + console.log(songInfo); + + + if(err){ + // TODO: Do something + } + + + songInfo.filePath = rootDir + thisSong.filepath.substring(startdir.length); + songInfo.format = thisSong.extension; + + arrayOfSongs.push(songInfo); + + + // if there are more than 100 entries, or if it's the last song + if(arrayOfSongs.length > 99 || fileCounter == fileCount){ + insertEntries(); + } + + // Remove the scanlock if it's the last song + if(fileCounter == fileCount){ + scanLock = false; + } + + }); +} + + + + + + + + + + + // Counts the number of open files -var maxFileCounterThing = 0; +//var maxFileCounterThing = 0; + +// function recursiveScan(dir, fileTypesArray){ +// var files = fs.readdirSync( dir ); + +// // loop through files +// for (var i=0; i < files.length; i++) { +// var filePath = dir + files[i]; +// var stat = fs.statSync(filePath); + + +// if(stat.isDirectory()){ +// recursiveScan(filePath + '/', fileTypesArray); +// }else{ +// var extension = getFileType(files[i]); + +// // Make sure this is in our list of allowed files +// if (fileTypesArray.indexOf(extension) > -1 ) { +// parseFile(filePath, files[i], extension); +// } +// } + +// } +// } + +// // This function checks if there's too many files open +// function parseFile(filePath, filename, extension){ + +// // Limits number offiles open +// if(maxFileCounterThing > 25){ + +// // Wait 3 seconds and try again. TODO: Make this a config variable +// setTimeout(function () { +// parseFile(filePath, filename, extension) +// }, 3000); +// }else{ +// // Increment counter +// maxFileCounterThing++; +// // Parse file +// parseFile2(filePath, filename, extension); +// } + +// } + +// // Pull song info from file +// function parseFile2(filePath, filename, extension){ + +// // TODO: Test what happens when an error occurs +// var parser = metadata(fs.createReadStream(filePath), function (err, songInfo) { +// fileCounter++; + +// console.log(songInfo); + + +// if(err){ +// // TODO: Do something +// } + + +// songInfo.filePath = filePath.substring(startdir.length); +// songInfo.filename = filename; +// songInfo.filetype = extension; + +// arrayOfSongs.push(songInfo); + + +// // if there are more than 100 entries, or if it's the last song +// if(arrayOfSongs.length > 100 || fileCounter == fileCount){ +// insertEntries(); +// } + +// // Remove the scanlock if it's the last song +// if(fileCounter == fileCount){ +// scanLock = false; +// } + +// // +// maxFileCounterThing--; + +// }); +// } -// This function checks if there's too many files open -function parseFile(filePath, filename, extension){ - // Limits number offiles open - if(maxFileCounterThing > 150){ - - // Wait 3 seconds and try again. TODO: Make this a config variable - setTimeout(function () { - parseFile(filePath, filename, extension) - }, 3000); - }else{ - // Increment counter - maxFileCounterThing++; - // Parse file - parseFile2(filePath, filename, extension); - } - -} - -// Pull song info from file -function parseFile2(filePath, filename, extension){ - - // TODO: Test what happens when an error occurs - var parser = metadata(fs.createReadStream(filePath), function (err, songInfo) { - fileCounter++; - - console.log(songInfo); - - - if(err){ - // TODO: Do something - } - - - songInfo.filePath = filePath.substring(startdir.length); - songInfo.filename = filename; - songInfo.filetype = extension; - - arrayOfSongs.push(songInfo); - - - // if there are more than 20 entries, or if it's the last song - if(arrayOfSongs.length > 100 || fileCounter == fileCount){ - insertEntries(); - } - - // Remove the scanlock if it's the last song - if(fileCounter == fileCount){ - scanLock = false; - } - - // - maxFileCounterThing--; - - }); -} // Insert function insertEntries(){ - var sql2 = "insert into files (title,artist,year,album,filename,file_location,filetype) values "; - var sqlParser = []; + var sql2 = "insert into items (title,artist,year,album,path,format, track) values "; + var sqlParser = []; - while(arrayOfSongs.length > 0) { - var song = arrayOfSongs.pop(); + while(arrayOfSongs.length > 0) { + var song = arrayOfSongs.pop(); - console.log(song); + console.log(song); - var songTitle = null; - var songYear = null; - var songAlbum = null; - var artistString = null; - - if(song.artist && song.artist.length > 0){ - artistString = ''; - for (var i = 0; i < song.artist.length; i++) { - artistString += song.artist[i] + ', '; - } - artistString = artistString.slice(0, -2); - } - if(song.title && song.title.length > 0){ - songTitle = song.title; - } - if(song.year && song.year.length > 0){ - songYear = song.year; - } - if(song.album && song.album.length > 0){ - songAlbum = song.album; - } - - - sql2 += "(?, ?, ?, ?, ?, ?, ?), "; - sqlParser.push(songTitle); - sqlParser.push(artistString); - sqlParser.push(songYear); - sqlParser.push(songAlbum); - sqlParser.push(song.filename); - sqlParser.push(song.filePath); - sqlParser.push(song.filetype); - + var songTitle = null; + var songYear = null; + var songAlbum = null; + var artistString = null; + if(song.artist && song.artist.length > 0){ + artistString = ''; + for (var i = 0; i < song.artist.length; i++) { + artistString += song.artist[i] + ', '; + } + artistString = artistString.slice(0, -2); + } + if(song.title && song.title.length > 0){ + songTitle = song.title; + } + if(song.year && song.year.length > 0){ + songYear = song.year; + } + if(song.album && song.album.length > 0){ + songAlbum = song.album; } - sql2 = sql2.slice(0, -2); - sql2 += ";"; - console.log(sql2); - db.run(sql2, sqlParser); + sql2 += "(?, ?, ?, ?, ?, ?, ?), "; + sqlParser.push(songTitle); + sqlParser.push(artistString); + sqlParser.push(songYear); + sqlParser.push(songAlbum); + sqlParser.push(song.filePath); + sqlParser.push(song.format); + sqlParser.push(song.track.no) + + } + + sql2 = sql2.slice(0, -2); + sql2 += ";"; + + console.log(sql2); + db.run(sql2, sqlParser); } // Count all files function countFiles (dir, fileTypesArray) { - var files = fs.readdirSync( dir ); + var files = fs.readdirSync( dir ); + for (var i=0; i < files.length; i++) { + var filePath = dir + files[i]; + var stat = fs.statSync(filePath); - for (var i=0; i < files.length; i++) { - var filePath = dir + files[i]; - var stat = fs.statSync(filePath); - - if(stat.isDirectory()){ - countFiles(filePath + '/', fileTypesArray); - }else{ - var extension = getFileType(files[i]); - // var extension = files[i].substr(files[i].length - 3); - // // compensate for flac - // if(extension === 'lac'){ - // extension = 'flac'; - // } - - if (fileTypesArray.indexOf(extension) > -1 ) { - fileCount++; - } - } + if(stat.isDirectory()){ + countFiles(filePath + '/', fileTypesArray); + }else{ + var extension = getFileType(files[i]); + if (fileTypesArray.indexOf(extension) > -1 ) { + fileCount++; + } } + + } } - - -// TODO: Search app.post('/db/search', function(req, res){ - var searchTerm = "%" + req.body.search + "%" ; - console.log(searchTerm); + var searchTerm = "%" + req.body.search + "%" ; - var artists; - var albums; + var returnThis = {"albums":[], "artists":[]}; - var returnThis = {"albums":[], "artists":[]}; - - // TODO: Combine SQL calls into one - db.serialize(function() { - var sqlAlbum = "SELECT DISTINCT album FROM files WHERE files.album LIKE ? ORDER BY album COLLATE NOCASE ASC;"; - db.all(sqlAlbum, searchTerm, function(err, rows) { - console.log(err); - console.log(rows); - - for (var i = 0; i < rows.length; i++) { - if(rows[i].album){ - // rows.splice(i, 1); - returnThis.albums.push(rows[i].album); - } - } - }); - - - var sqlAlbum = "SELECT DISTINCT artist FROM files WHERE files.artist LIKE ? ORDER BY artist COLLATE NOCASE ASC;"; - db.all(sqlAlbum, searchTerm, function(err, rows) { - console.log(err); - console.log(rows); - artists = rows; - - console.log('XXXXXXYYYYYYYZZZZZZZZ'); - - for (var i = 0; i < rows.length; i++) { - if(rows[i].artist){ - // rows.splice(i, 1); - returnThis.artists.push(rows[i].artist); - } - } - - console.log(returnThis); - res.send(JSON.stringify(returnThis)); - - }); + // TODO: Combine SQL calls into one + db.serialize(function() { + + var sqlAlbum = "SELECT DISTINCT album FROM items WHERE items.album LIKE ? ORDER BY album COLLATE NOCASE ASC;"; + db.all(sqlAlbum, searchTerm, function(err, rows) { + if(err){ + res.status(500).json({ error: 'DB Error' }); + return; + } + for (var i = 0; i < rows.length; i++) { + if(rows[i].album){ + // rows.splice(i, 1); + returnThis.albums.push(rows[i].album); + } + } }); + + var sqlAlbum = "SELECT DISTINCT artist FROM items WHERE items.artist LIKE ? ORDER BY artist COLLATE NOCASE ASC;"; + db.all(sqlAlbum, searchTerm, function(err, rows) { + if(err){ + res.status(500).json({ error: 'DB Error' }); + return; + } + + for (var i = 0; i < rows.length; i++) { + if(rows[i].artist){ + // rows.splice(i, 1); + returnThis.artists.push(rows[i].artist); + } + } + + res.send(JSON.stringify(returnThis)); + + }); + }); }); app.get('/db/artists', function (req, res) { - var sql = "SELECT DISTINCT artist FROM files ORDER BY artist COLLATE NOCASE ASC;"; + var sql = "SELECT DISTINCT artist FROM items ORDER BY artist COLLATE NOCASE ASC;"; - var artists = {"artists":[]}; + var artists = {"artists":[]}; - db.all(sql, function(err, rows) { + db.all(sql, function(err, rows) { + if(err){ + res.status(500).json({ error: 'DB Error' }); + return; + } - var returnArray = []; - for (var i = 0; i < rows.length; i++) { - if(rows[i].artist){ - // rows.splice(i, 1); - artists.artists.push(rows[i].artist); - } - } + var returnArray = []; + for (var i = 0; i < rows.length; i++) { + if(rows[i].artist){ + // rows.splice(i, 1); + artists.artists.push(rows[i].artist); + } + } - console.log(JSON.stringify(artists)); - res.send(JSON.stringify(artists)); - }); + res.send(JSON.stringify(artists)); + }); }); + + app.post('/db/artists-albums', function (req, res) { - var sql = "SELECT DISTINCT album FROM files WHERE artist = ? ORDER BY album COLLATE NOCASE ASC;"; + var sql = "SELECT DISTINCT album FROM items WHERE artist = ? ORDER BY album COLLATE NOCASE ASC;"; - var searchTerm = req.body.artist ; + var searchTerm = req.body.artist ; - var albums = {"albums":[]}; + var albums = {"albums":[]}; - // TODO: Make a list of all songs without null albums and add them to the response - db.all(sql, searchTerm, function(err, rows) { - console.log(rows); + // TODO: Make a list of all songs without null albums and add them to the response - var returnArray = []; - for (var i = 0; i < rows.length; i++) { - if(rows[i].album){ - // rows.splice(i, 1); - albums.albums.push(rows[i].album); - } - } + db.all(sql, searchTerm, function(err, rows) { + if(err){ + res.status(500).json({ error: 'DB Error' }); + return; + } - console.log(JSON.stringify(albums)); - res.send(JSON.stringify(albums)); - }); + var returnArray = []; + for (var i = 0; i < rows.length; i++) { + if(rows[i].album){ + // rows.splice(i, 1); + albums.albums.push(rows[i].album); + } + } + + res.send(JSON.stringify(albums)); + }); }); app.get('/db/albums', function (req, res) { - var sql = "SELECT DISTINCT album FROM files ORDER BY album COLLATE NOCASE ASC;"; + var sql = "SELECT DISTINCT album FROM items ORDER BY album COLLATE NOCASE ASC;"; - var albums = {"albums":[]}; + var albums = {"albums":[]}; - db.all(sql, function(err, rows) { + db.all(sql, function(err, rows) { + if(err){ + res.status(500).json({ error: 'DB Error' }); + return; + } - var returnArray = []; - for (var i = 0; i < rows.length; i++) { - if(rows[i].album){ - // rows.splice(i, 1); - albums.albums.push(rows[i].album); - } - } + var returnArray = []; + for (var i = 0; i < rows.length; i++) { + if(rows[i].album){ + albums.albums.push(rows[i].album); - console.log(JSON.stringify(albums)); - res.send(JSON.stringify(albums)); - }); + } + } + + console.log(JSON.stringify(albums)); + res.send(JSON.stringify(albums)); + }); }); + + app.post('/db/album-songs', function (req, res) { - var sql = "SELECT * FROM files WHERE album = ? ORDER BY filename COLLATE NOCASE ASC;"; + var sql = "SELECT title, artist, album, format, year, cast(path as TEXT), track FROM items WHERE album = ? ORDER BY track ASC;"; var searchTerm = req.body.album ; + + db.all(sql, searchTerm, function(err, rows) { - console.log(rows); + if(err){ + res.status(500).json({ error: 'DB Error' }); + return; + } + + // Format data for API + rows = setLocalFileLocation(rows); + + res.send(JSON.stringify(rows)); }); }); + + +function setLocalFileLocation(rows){ + var n = rootDir.length; + + for(var i in rows ){ + var path = String(rows[i]['cast(path as TEXT)']); + + rows[i].format = rows[i].format.toLowerCase(); // make sure the format is lowecase + rows[i].file_location = path.substring(n); // Get the local file location + rows[i].filename = path.split("/").pop(); // Ge the filane + } + + return rows; +} + + + // GET DB Status app.get('/db/status', function(req, res){ - var returnObject = {}; + var returnObject = {}; - var sql = 'SELECT Count(*) FROM files'; + var sql = 'SELECT Count(*) FROM files'; - db.get(sql, function(err, row){ - var fileCountDB = row.namesCount; // TODO: Is this correct??? + db.get(sql, function(err, row){ + if(err){ + res.status(500).json({ error: 'DB Error' }); + return; + } - returnObject.locked = scanLock; - returnObject.totalFileCount = fileCount; - returnObject.dbFileCount = fileCountDB; + var fileCountDB = row.namesCount; // TODO: Is this correct??? - res.json(returnObject); - }); + returnObject.locked = scanLock; + returnObject.totalFileCount = fileCount; + returnObject.dbFileCount = fileCountDB; + res.json(returnObject); + }); }); @@ -877,5 +994,5 @@ app.get('/db/status', function(req, res){ var server = app.listen(port, function () { // var host = server.address().address; // var port = server.address().port; - // console.log('Example app listening at http://%s:%s', host, port); -}); \ No newline at end of file + // console.log('Example app listening at http://%s:%s', host, port); +}); diff --git a/package.json b/package.json index 2d7802f..9d58c7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mstream", - "version": "1.10.1", + "version": "1.14.2", "description": "music streaming server", "main": "mstream.js", "bin": { @@ -16,20 +16,22 @@ "author": "iros", "license": "MIT", "dependencies": { - "archiver": "^0.20.0", - "bcrypt": "^0.8.5", - "body-parser": "^1.14.1", + "archiver": "^1.0.0", + "bcrypt": "^0.8.6", + "body-parser": "^1.15.1", "commander": "^2.9.0", "cookie-parser": "^1.4.0", - "express": "^4.13.3", + "express": "^4.13.4", "express-session": "^1.12.1", - "external-ip": "^0.2.4", - "graceful-fs": "^4.1.3", + "graceful-fs": "^4.1.4", "musicmetadata": "^2.0.2", - "nat-pmp": "0.0.3", - "netroute": "^1.0.2", "passport": "^0.3.2", "passport-local": "^1.0.0", - "sqlite3": "^3.1.3" + "sqlite3": "^3.1.4" + }, + "optionalDependencies": { + "nat-pmp": "1.0.0", + "netroute": "^1.0.2", + "external-ip": "^0.2.4" } } diff --git a/public/css/master.css b/public/css/master.css index 6bb8a68..02665f5 100755 --- a/public/css/master.css +++ b/public/css/master.css @@ -8,10 +8,22 @@ body { .clear { clear: both; } -.fullHeight, .main-section, .libraryColumn, .playlistColumn { +.fullHeight, .main-section, .playlistColumn { height: 100%; overflow: scroll; } +.libraryColumn { + height: 100%; + +} + +.testScroll { + overflow: scroll; + height: 100%; + padding-bottom: 120px; + +} + .button { margin-left: 10px; border-radius: 100px; diff --git a/public/js/mstream.js b/public/js/mstream.js index 6d18f1a..0151351 100755 --- a/public/js/mstream.js +++ b/public/js/mstream.js @@ -231,7 +231,7 @@ $(document).ready(function(){ fileExplorerArray.push(nextDir); // Save the scroll position - var scrollPosition = $('.libraryColumn').scrollTop(); + var scrollPosition = $('.testScroll').scrollTop(); fileExplorerScrollPosition.push(scrollPosition); //pass this value along @@ -272,7 +272,7 @@ $(document).ready(function(){ printdir(response); // Set scroll postion - $('.libraryColumn').scrollTop(scrollPosition); + $('.testScroll').scrollTop(scrollPosition); }); } @@ -633,10 +633,10 @@ $("#filelist").on('click', '.playlistz', function() { $.each(parsedMessage, function() { console.log(this); if(this.title==null){ - filelist.push('
'+this.filename+'
'); + filelist.push('
'+this.filename+'
'); } else{ - filelist.push('
'+this.title+'
'); + filelist.push('
'+this.title+'
'); } }); diff --git a/public/mstream.html b/public/mstream.html index 6c586de..ae9acb7 100755 --- a/public/mstream.html +++ b/public/mstream.html @@ -137,7 +137,7 @@ background-color: #E6EBFA !important; -
+
Nothing Here...