From e252bbee146f8358865e39f99ef5f684c9adb591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wo=C5=BAniak?= Date: Sat, 26 Oct 2019 11:30:36 +0200 Subject: [PATCH 1/3] Playing and downloading m3u playlists --- modules/file-explorer.js | 109 ++++++++++++++++++++-- public/css/master.css | 22 ++++- public/css/mstream-player.css | 20 ++-- public/js/api2.js | 8 ++ public/js/mstream.js | 167 ++++++++++++++++++++++------------ 5 files changed, 243 insertions(+), 83 deletions(-) diff --git a/modules/file-explorer.js b/modules/file-explorer.js index ec065c7..ee8c145 100644 --- a/modules/file-explorer.js +++ b/modules/file-explorer.js @@ -4,10 +4,60 @@ const fe = require("path"); const archiver = require('archiver'); const winston = require('winston'); const mkdirp = require('make-dir'); +const m3uread = require('m3u8-reader') -const masterFileTypesArray = ["mp3", "flac", "wav", "ogg", "aac", "m4a", "opus"]; +const masterFileTypesArray = ["mp3", "flac", "wav", "ogg", "aac", "m4a", "opus", "m3u"]; exports.setup = function(mstream, program) { + + function getPathInfoOrThrow(req, path) { + const pathInfo = program.getVPathInfo(path); + if (pathInfo == false) { + throw {code: 500, json: { error: "Could not find file" }}; + } + if (!req.user.vpaths.includes(pathInfo.vpath)) { + throw {code: 500, json: { error: "Access Denied" }}; + } + return pathInfo; + } + + function getPathArray(path) { + return path.split("/").filter(Boolean) + } + + function getParentDirPath(path) { + return getPathArray(path).slice(0, -1).join("/"); + } + + function readPlaylistSongs(path) { + return m3uread(fs.readFileSync(path)) + .filter(function (item) { return typeof item === "string" }) + .map(function (item) { return item.replace(/\\/g, "/") }) // m3u path separated by \ + } + + function getFileName(path) { + return getPathArray(path).pop() + } + + function joinPaths(path1, path2) { + return getPathArray(path1).concat(getPathArray(path2)).join("/") + } + + function handleError(error, res, next) { + if (error.code && error.json) { + res.status(error.code).json(error.json); + } else { + next(error); + } + } + + function setArchiverErrorHandler(archive, res) { + archive.on('error', function (err) { + winston.error(`Download Error: ${err.message}`); + res.status(500).json({ error: err.message }); + }); + } + mstream.post('/download-directory', (req, res) => { if (!req.body.directory) { return res.status(500).json({ error: 'Missing Params' }); @@ -33,11 +83,7 @@ exports.setup = function(mstream, program) { } const archive = archiver('zip'); - - archive.on('error', function (err) { - winston.error(`Download Error: ${err.message}`); - res.status(500).json({ error: err.message }); - }); + setArchiverErrorHandler(archive, res); // sets the archive name. TODO: Rename this res.attachment('zipped-playlist.zip'); @@ -48,6 +94,26 @@ exports.setup = function(mstream, program) { archive.finalize(); }); + mstream.post('/fileplaylist/download', (req, res, next) => { + try { + const playlistPathInfo = getPathInfoOrThrow(req, req.body.path); + const playlistParentDir = getParentDirPath(req.body.path); + const songs = readPlaylistSongs(playlistPathInfo.fullPath); + const archive = archiver('zip'); + setArchiverErrorHandler(archive, res); + res.attachment(getFileName(req.body.path) + ".zip"); + archive.pipe(res); + for (let song of songs) { + const songPath = joinPaths(playlistParentDir, song); + const songPathInfo = getPathInfoOrThrow(req, songPath); + archive.file(songPathInfo.fullPath, { name: getFileName(song) }) + } + archive.finalize(); + } catch (error) { + handleError(error, res, next); + } + }); + mstream.post("/upload", function (req, res) { if (program.noUpload) { return res.status(500).json({ error: 'Uploading Disabled' }); @@ -83,7 +149,33 @@ exports.setup = function(mstream, program) { return req.pipe(busboy); }); - + + mstream.post("/fileplaylist/load", function(req, res, next) { + try { + const playlistPathInfo = getPathInfoOrThrow(req, req.body.path); + const playlistParentDir = getParentDirPath(req.body.path); + const songs = readPlaylistSongs(playlistPathInfo.fullPath); + res.json({ + contents: songs.map(function (song) { + return {type: getFileType(song), name: getFileName(song), path: joinPaths(playlistParentDir, song)} + }) + }) + } catch (error) { + handleError(error, res, next); + } + }) + + mstream.post("/fileplaylist/loadpaths", function(req, res, next) { + try { + const playlistPathInfo = getPathInfoOrThrow(req, req.body.path); + const playlistParentDir = getParentDirPath(req.body.path); + const songs = readPlaylistSongs(playlistPathInfo.fullPath); + res.json(songs.map(function (song) { return joinPaths(playlistParentDir, song); })); + } catch (error) { + handleError(error, res, next); + } + }) + // parse directories mstream.post("/dirparser", function(req, res) { const directories = []; @@ -119,7 +211,6 @@ exports.setup = function(mstream, program) { return; } - // Will only show these files. Prevents people from snooping around var fileTypesArray; if (req.body.filetypes) { fileTypesArray = req.body.filetypes; @@ -222,7 +313,7 @@ exports.setup = function(mstream, program) { } else { const extension = getFileType(file).toLowerCase(); if (fileTypesArray.indexOf(extension) > -1 && masterFileTypesArray.indexOf(extension) > -1) { - filelist.push(fe.join(pathInfo.vpath, fe.join(relativePath, file))); + filelist.push(fe.join(pathInfo.vpath, fe.join(relativePath, file))); } } }); diff --git a/public/css/master.css b/public/css/master.css index bc040a1..f7d3991 100755 --- a/public/css/master.css +++ b/public/css/master.css @@ -163,7 +163,7 @@ div#jp_container_N { box-shadow: 0 0 10px #D6D6D6; padding: 10px; } -.filez, .dirz, .back, .artistz, .albumz, .playlist_row_container, .playlist-item, #playlist_container ul li { +.filez, .dirz, .fileplaylistz, .back, .artistz, .albumz, .playlist_row_container, .playlist-item, #playlist_container ul li { font-family: Arial, Helvetica Neue, Helvetica, sans-serif; cursor: pointer; width: 100%; @@ -182,6 +182,13 @@ div#jp_container_N { overflow: visible; padding: 10px; } + +.fileplaylistz { + float: left; + position: relative; + overflow: visible; + padding: 10px; } + .playlistz{ display: block; padding: 10px; @@ -248,7 +255,7 @@ div#jp_container_N { clear: both; } -.dirz:hover, .albumz:hover, .artistz:hover, .playlistz:hover { +.dirz:hover, .albumz:hover, .artistz:hover, .playlistz:hover, .fileplaylistz:hover { background-color: rgba(230, 154, 23, 0.15); } .back:hover { @@ -560,6 +567,11 @@ h3 { margin-right: 5px; } +.fileplaylist-image{ + height: 21px; + margin-right: 5px; +} + .item-text{ vertical-align: middle; } @@ -705,7 +717,7 @@ h3 { display: none; } - .tab-bar-section { + .tab-bar-section { margin: 0; padding: 0 !important; } @@ -744,7 +756,7 @@ ul.left-nav-menu li { color: #BBB; cursor: pointer; font-weight: 800; - + font-family: "Jura", sans-serif; } @@ -835,4 +847,4 @@ ul.left-nav-menu li.selected svg { #federation-invite-selection-panel { overflow-y: scroll; max-height: 300px; -} \ No newline at end of file +} diff --git a/public/css/mstream-player.css b/public/css/mstream-player.css index 39c65b1..81c0e16 100644 --- a/public/css/mstream-player.css +++ b/public/css/mstream-player.css @@ -115,11 +115,11 @@ top: 0px; } -.songDropdown:hover, .downloadPlaylistSong:hover, .recursiveAddDir:hover { +.songDropdown:hover, .downloadPlaylistSong:hover, .recursiveAddDir:hover, .addFileplaylist:hover { background-color: #9E9E9E; } -.songDropdown, .downloadPlaylistSong, .recursiveAddDir{ +.songDropdown, .downloadPlaylistSong, .recursiveAddDir, .addFileplaylist { height: 14px; background-color: #B5B5B5; float: right; @@ -131,20 +131,20 @@ cursor: pointer; } -.downloadPlaylistSong, .recursiveAddDir { +.downloadPlaylistSong, .recursiveAddDir, .addFileplaylist { min-width: 28px; border-right: 1px solid #9E9E9E; } -.songDropdown { +.songDropdown { min-width: 38px !important; } -.popperMenu:hover, .downloadDir:hover { +.popperMenu:hover, .downloadDir:hover, .downloadFileplaylist:hover { background-color: #9E9E9E; } -.popperMenu, .downloadDir { +.popperMenu, .downloadDir, .downloadFileplaylist { min-width: 28px !important; height: 14px; background-color: #B5B5B5; @@ -159,8 +159,6 @@ cursor: pointer; } - - #pop-d { min-width: 50px; background-color: #F5F5F5; @@ -237,7 +235,7 @@ } .drag-handle img{ - width: 20px; + width: 20px; } .titlebar{ @@ -380,13 +378,13 @@ fill: rgb(255, 255, 255); } @media (max-width: 450px) { .volume-bar { - display: none;} + display: none;} .remote-button { display: none;} } @media (max-device-width: 451px) { .volume-bar { - display: none;} + display: none;} .remote-button { display: none;} } diff --git a/public/js/api2.js b/public/js/api2.js index 88951d2..cd53d0f 100644 --- a/public/js/api2.js +++ b/public/js/api2.js @@ -45,6 +45,14 @@ var MSTREAMAPI = (function () { makePOSTRequest('/dirparser', { dir: directory }, callback); } + mstreamModule.loadFileplaylist = function (path, callback) { + makePOSTRequest('/fileplaylist/load', { path }, callback); + } + + mstreamModule.loadFileplaylistPaths = function (path, callback) { + makePOSTRequest('/fileplaylist/loadpaths', { path }, callback); + } + mstreamModule.recursiveScan = function (directory, filetypes, callback) { makePOSTRequest('/files/recursive-scan', { dir: directory }, callback); } diff --git a/public/js/mstream.js b/public/js/mstream.js index c95c031..6275c4f 100755 --- a/public/js/mstream.js +++ b/public/js/mstream.js @@ -154,11 +154,7 @@ $(document).ready(function () { }); myDropzone.removeFile(file); } else { - var directoryString = ""; - for (var i = 0; i < fileExplorerArray.length; i++) { - directoryString += fileExplorerArray[i] + "/"; - } - file.directory = directoryString + file.fullPath.substring(0, file.fullPath.indexOf(file.name)); + file.directory = getFileExplorerPath(fileExplorerArray) + file.fullPath.substring(0, file.fullPath.indexOf(file.name)); } }); @@ -254,7 +250,7 @@ $(document).ready(function () { this.pending = true; var that = this; MSTREAMAPI.login($('#login-username').val(), $('#login-password').val(), function (response, error) { - that.pending = false; + that.pending = false; if (error !== false) { // Alert the user iziToast.error({ @@ -269,7 +265,7 @@ $(document).ready(function () { if (typeof(Storage) !== "undefined") { localStorage.setItem("token", response.token); } - + // Add the token the URL calls MSTREAMAPI.updateCurrentServer($('#login-username').val(), response.token, response.vpaths) @@ -392,7 +388,7 @@ $(document).ready(function () { if (err.responseJSON && err.responseJSON.error) { msg = err.responseJSON.error; } - + iziToast.error({ title: msg, position: 'topCenter', @@ -429,22 +425,58 @@ $(document).ready(function () { senddir(null, fileExplorerArray); } + function createNewFileExplorerArray(nextDir) { + return fileExplorerArray.concat(nextDir); + + // old impl, todo: ask Paul if we can use modern js in webapp. + // var newArray = []; + // for (var i = 0; i < fileExplorerArray.length; i++) { + // newArray.push(fileExplorerArray[i]); + // } + // newArray.push(nextDir); + // return newArray; + } + + function setScrollPosition(scrollPosition) { + if (scrollPosition === false) { + var sp = $('#filelist').scrollTop(); + fileExplorerScrollPosition.push(sp); + $('#filelist').scrollTop(0); + } else if (scrollPosition === true) { + var sp = fileExplorerScrollPosition.pop(); + $('#filelist').scrollTop(sp); + } + } + // Load up the file explorer $('.get_file_explorer').on('click', loadFileExplorer); // when you click on a directory, go to that directory $("#filelist").on('click', 'div.dirz', function () { - //get the id of that class - var nextDir = $(this).data("directory"); - var newArray = []; - for (var i = 0; i < fileExplorerArray.length; i++) { - newArray.push(fileExplorerArray[i]); - } - newArray.push(nextDir); - + var newArray = createNewFileExplorerArray($(this).data("directory")); senddir(false, newArray); }); + // when you click on a playlist, go to that playlist + $("#filelist").on('click', 'div.fileplaylistz', function () { + var newArray = createNewFileExplorerArray($(this).data("directory")); + var directoryString = getFileExplorerPath(newArray); + + $('.directoryName').html('/' + directoryString); + $('#filelist').html('
'); + + MSTREAMAPI.loadFileplaylist(directoryString, function (response, error) { + if (error !== false) { + boilerplateFailure(response, error); + return; + } + + fileExplorerArray = newArray; + printdir(response); + setScrollPosition(false); + }); + }); + // when you click the back directory $(".backButton").on('click', function () { // Handle file Explorer @@ -482,10 +514,7 @@ $(document).ready(function () { // send a new directory to be parsed. function senddir(scrollPosition, newArray) { // Construct the directory string - var directoryString = ""; - for (var i = 0; i < newArray.length; i++) { - directoryString += newArray[i] + "/"; - } + var directoryString = getFileExplorerPath(newArray); $('.directoryName').html('/' + directoryString); $('#filelist').html('
'); @@ -500,15 +529,7 @@ $(document).ready(function () { // Set any directory views // hand this data off to be printed on the page printdir(response); - // Set scroll postion - if (scrollPosition === false) { - var sp = $('#filelist').scrollTop(); - fileExplorerScrollPosition.push(sp); - $('#filelist').scrollTop(0); - } else if (scrollPosition === true) { - var sp = fileExplorerScrollPosition.pop(); - $('#filelist').scrollTop(sp); - } + setScrollPosition(scrollPosition); }); } @@ -523,13 +544,16 @@ $(document).ready(function () { //parse through the json array and make an array of corresponding divs var filelist = []; $.each(currentBrowsingList, function () { + const fileLocation = this.path || response.path + this.name; if (this.type == 'directory') { filelist.push('
' + this.name + '
'); } else { - if (this.artist != null || this.title != null) { - filelist.push('
' + this.artist + ' - ' + this.title + '
'); + if (this.type == 'm3u') { + filelist.push('
' + this.name + '
'); + } else if (this.artist != null || this.title != null) { + filelist.push('
' + this.artist + ' - ' + this.title + '
'); } else { - filelist.push('
' + this.name + '
'); + filelist.push('
' + this.name + '
'); } } }); @@ -620,27 +644,36 @@ $(document).ready(function () { } }); - $("#filelist").on('click', '.recursiveAddDir', function () { - var directoryString = "/"; - for (var i = 0; i < fileExplorerArray.length; i++) { - directoryString += fileExplorerArray[i] + "/"; - } + function getFileExplorerPath(explorerArray) { + return explorerArray.join("/") + "/"; + } - directoryString += $(this).data("directory"); + function getDirectoryString(component) { + return "/" + getFileExplorerPath(fileExplorerArray) + component.data("directory"); + } + + function addAllSongs(res) { + for (var i = 0; i < res.length; i++) { + MSTREAMAPI.addSongWizard(res[i], {}, true); + } + } + + $("#filelist").on('click', '.recursiveAddDir', function () { + var directoryString = getDirectoryString($(this)); MSTREAMAPI.recursiveScan(directoryString, false, function(res, err){ - for (var i = 0; i < res.length; i++) { - MSTREAMAPI.addSongWizard(res[i], {}, true); - } + addAllSongs(res); + }); + }); + + $("#filelist").on('click', '.addFileplaylist', function () { + var playlistPath = getDirectoryString($(this)); + MSTREAMAPI.loadFileplaylistPaths(playlistPath, function(res, err){ + addAllSongs(res); }); }); $("#filelist").on('click', '.downloadDir', function () { - var directoryString = "/"; - for (var i = 0; i < fileExplorerArray.length; i++) { - directoryString += fileExplorerArray[i] + "/"; - } - - directoryString += $(this).data("directory"); + var directoryString = getDirectoryString($(this)); // Use key if necessary $("#downform").attr("action", "/download-directory?token=" + MSTREAMAPI.currentServer.token); @@ -657,6 +690,24 @@ $(document).ready(function () { $('#downform').empty(); }); + $("#filelist").on('click', '.downloadFileplaylist', function () { + var playlistPath = getDirectoryString($(this)); + + // Use key if necessary + $("#downform").attr("action", "/fileplaylist/download?token=" + MSTREAMAPI.currentServer.token); + + $('').attr({ + type: 'hidden', + name: 'path', + value: playlistPath, + }).appendTo('#downform'); + + //submit form + $('#downform').submit(); + // clear the form + $('#downform').empty(); + }); + ////////////////////////////////////// Share playlists $('#share_playlist_form').on('submit', function (e) { e.preventDefault(); @@ -840,7 +891,7 @@ $(document).ready(function () { $('#filelist').html('
Server call failed
'); return boilerplateFailure(response, error); } - + // Add the playlist name to the modal $('#playlist_name').val(name); @@ -982,7 +1033,7 @@ $(document).ready(function () { resetPanel('Recently Added', 'scrollBoxHeight1'); $('#filelist').html('
'); $('.directoryName').html('Get last      songs'); - + redoRecentlyAdded(); } @@ -1166,7 +1217,7 @@ $(document).ready(function () { }); $('#filelist').html(albums); - // update lazy load plugin + // update lazy load plugin ll.update(); }); } @@ -1238,8 +1289,8 @@ $(document).ready(function () { newHtml += '

Default Bitrate: '+MSTREAMAPI.transcodeOptions.bitrate+'

\

Default Codec: '+MSTREAMAPI.transcodeOptions.codec+'

'; - - if (MSTREAMAPI.transcodeOptions.frontendEnabled) { + + if (MSTREAMAPI.transcodeOptions.frontendEnabled) { newHtml += '

'; } else { newHtml += '

'; @@ -1267,7 +1318,7 @@ $(document).ready(function () { // Convert playlist for (let i = 0; i < MSTREAMPLAYER.playlist.length; i++) { - MSTREAMPLAYER.playlist[i].url = MSTREAMPLAYER.playlist[i].url.replace(a, b); + MSTREAMPLAYER.playlist[i].url = MSTREAMPLAYER.playlist[i].url.replace(a, b); } // re-enable checkbox @@ -1286,7 +1337,7 @@ $(document).ready(function () { var newHtml = '\

Federation allows you easily sync folders between mStream servers or the backup tool. Federation is a one-way process. When you invite someone, they can only read the federated folders. Any changes they make will not be sent to your mStream server.

\

Federation is powered by Syncthing

'; - + if (federationId) { newHtml += '\

Federation ID: '+federationId+'

\ @@ -1296,7 +1347,7 @@ $(document).ready(function () { }else { newHtml += '

Federation is Disabled

'; } - + $('#filelist').html(newHtml); }); @@ -1311,7 +1362,7 @@ $(document).ready(function () { $('#filelist').on('click', '.trigger-generate-invite-public', function() { $('.invite-federation-id').addClass('super-hide'); $('.invite-federation-url').removeClass('super-hide'); - + $('#invite-public-url').prop('disabled', false); $('#invite-federation-id').prop('disabled', true); }); @@ -1331,7 +1382,7 @@ $(document).ready(function () { $('input[name="federate-this"]:checked').each(function () { vpaths.push($(this).val()); }); - + if(vpaths.length === 0) { iziToast.error({ title: 'Nothing to Federate', @@ -1413,7 +1464,7 @@ $(document).ready(function () { title: 'No directories selected', position: 'topCenter', timeout: 3500 - }); + }); } var sendThis = { From 0168947ff3f58840521da9e855385cd51ae7223a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wo=C5=BAniak?= Date: Mon, 28 Oct 2019 20:34:44 +0100 Subject: [PATCH 2/3] Playing and downloading m3u playlists - review fixes --- modules/file-explorer.js | 76 ++++++++++++++++------------------------ public/js/mstream.js | 16 ++------- 2 files changed, 33 insertions(+), 59 deletions(-) diff --git a/modules/file-explorer.js b/modules/file-explorer.js index ee8c145..efcb652 100644 --- a/modules/file-explorer.js +++ b/modules/file-explorer.js @@ -4,14 +4,15 @@ const fe = require("path"); const archiver = require('archiver'); const winston = require('winston'); const mkdirp = require('make-dir'); -const m3uread = require('m3u8-reader') +const m3uread = require('m3u8-reader'); +const path = require('path'); const masterFileTypesArray = ["mp3", "flac", "wav", "ogg", "aac", "m4a", "opus", "m3u"]; exports.setup = function(mstream, program) { - function getPathInfoOrThrow(req, path) { - const pathInfo = program.getVPathInfo(path); + function getPathInfoOrThrow(req, pathString) { + const pathInfo = program.getVPathInfo(pathString); if (pathInfo == false) { throw {code: 500, json: { error: "Could not find file" }}; } @@ -21,43 +22,26 @@ exports.setup = function(mstream, program) { return pathInfo; } - function getPathArray(path) { - return path.split("/").filter(Boolean) + function getPathArray(pathString) { + return pathString.split(path.sep).filter(Boolean); } - function getParentDirPath(path) { - return getPathArray(path).slice(0, -1).join("/"); + function getFileType(pathString) { + return path.extname(pathString).substr(1); } - function readPlaylistSongs(path) { - return m3uread(fs.readFileSync(path)) + function readPlaylistSongs(pathString) { + return m3uread(fs.readFileSync(pathString)) .filter(function (item) { return typeof item === "string" }) - .map(function (item) { return item.replace(/\\/g, "/") }) // m3u path separated by \ + .map(function (item) { return item.replace(/\\/g, path.sep) }) // m3u path separated by \ } - function getFileName(path) { - return getPathArray(path).pop() - } - - function joinPaths(path1, path2) { - return getPathArray(path1).concat(getPathArray(path2)).join("/") - } - - function handleError(error, res, next) { + function handleError(error, res) { if (error.code && error.json) { res.status(error.code).json(error.json); - } else { - next(error); } } - function setArchiverErrorHandler(archive, res) { - archive.on('error', function (err) { - winston.error(`Download Error: ${err.message}`); - res.status(500).json({ error: err.message }); - }); - } - mstream.post('/download-directory', (req, res) => { if (!req.body.directory) { return res.status(500).json({ error: 'Missing Params' }); @@ -83,7 +67,10 @@ exports.setup = function(mstream, program) { } const archive = archiver('zip'); - setArchiverErrorHandler(archive, res); + archive.on('error', function (err) { + winston.error(`Download Error: ${err.message}`); + res.status(500).json({ error: err.message }); + }); // sets the archive name. TODO: Rename this res.attachment('zipped-playlist.zip'); @@ -97,20 +84,23 @@ exports.setup = function(mstream, program) { mstream.post('/fileplaylist/download', (req, res, next) => { try { const playlistPathInfo = getPathInfoOrThrow(req, req.body.path); - const playlistParentDir = getParentDirPath(req.body.path); + const playlistParentDir = path.dirname(req.body.path); const songs = readPlaylistSongs(playlistPathInfo.fullPath); const archive = archiver('zip'); - setArchiverErrorHandler(archive, res); - res.attachment(getFileName(req.body.path) + ".zip"); + archive.on('error', function (err) { + winston.error(`Download Error: ${err.message}`); + res.status(500).json({ error: err.message }); + }); + res.attachment(path.basename(req.body.path) + ".zip"); archive.pipe(res); for (let song of songs) { - const songPath = joinPaths(playlistParentDir, song); + const songPath = path.join(playlistParentDir, song); const songPathInfo = getPathInfoOrThrow(req, songPath); - archive.file(songPathInfo.fullPath, { name: getFileName(song) }) + archive.file(songPathInfo.fullPath, { name: path.basename(song) }) } archive.finalize(); } catch (error) { - handleError(error, res, next); + handleError(error, res); } }); @@ -153,26 +143,26 @@ exports.setup = function(mstream, program) { mstream.post("/fileplaylist/load", function(req, res, next) { try { const playlistPathInfo = getPathInfoOrThrow(req, req.body.path); - const playlistParentDir = getParentDirPath(req.body.path); + const playlistParentDir = path.dirname(req.body.path); const songs = readPlaylistSongs(playlistPathInfo.fullPath); res.json({ contents: songs.map(function (song) { - return {type: getFileType(song), name: getFileName(song), path: joinPaths(playlistParentDir, song)} + return {type: getFileType(song), name: path.basename(song), path: path.join(playlistParentDir, song)} }) }) } catch (error) { - handleError(error, res, next); + handleError(error, res); } }) mstream.post("/fileplaylist/loadpaths", function(req, res, next) { try { const playlistPathInfo = getPathInfoOrThrow(req, req.body.path); - const playlistParentDir = getParentDirPath(req.body.path); + const playlistParentDir = path.dirname(req.body.path); const songs = readPlaylistSongs(playlistPathInfo.fullPath); - res.json(songs.map(function (song) { return joinPaths(playlistParentDir, song); })); + res.json(songs.map(function (song) { return path.join(playlistParentDir, song); })); } catch (error) { - handleError(error, res, next); + handleError(error, res); } }) @@ -322,8 +312,4 @@ exports.setup = function(mstream, program) { res.json(recursiveTrot(pathInfo.fullPath, [], pathInfo.relativePath)); }); - - function getFileType(filename) { - return filename.split(".").pop(); - } }; diff --git a/public/js/mstream.js b/public/js/mstream.js index 6275c4f..34f0046 100755 --- a/public/js/mstream.js +++ b/public/js/mstream.js @@ -425,18 +425,6 @@ $(document).ready(function () { senddir(null, fileExplorerArray); } - function createNewFileExplorerArray(nextDir) { - return fileExplorerArray.concat(nextDir); - - // old impl, todo: ask Paul if we can use modern js in webapp. - // var newArray = []; - // for (var i = 0; i < fileExplorerArray.length; i++) { - // newArray.push(fileExplorerArray[i]); - // } - // newArray.push(nextDir); - // return newArray; - } - function setScrollPosition(scrollPosition) { if (scrollPosition === false) { var sp = $('#filelist').scrollTop(); @@ -453,13 +441,13 @@ $(document).ready(function () { // when you click on a directory, go to that directory $("#filelist").on('click', 'div.dirz', function () { - var newArray = createNewFileExplorerArray($(this).data("directory")); + var newArray = fileExplorerArray.concat($(this).data("directory")); senddir(false, newArray); }); // when you click on a playlist, go to that playlist $("#filelist").on('click', 'div.fileplaylistz', function () { - var newArray = createNewFileExplorerArray($(this).data("directory")); + var newArray = fileExplorerArray.concat($(this).data("directory")); var directoryString = getFileExplorerPath(newArray); $('.directoryName').html('/' + directoryString); From 73812510dbcab7dda5d8eb616022d4b304bac6f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wo=C5=BAniak?= Date: Tue, 29 Oct 2019 11:02:24 +0100 Subject: [PATCH 3/3] Playing and downloading m3u playlists - review fixes 2 --- modules/file-explorer.js | 23 ++++++++++---------- package.json | 1 + public/js/mstream.js | 46 ++++++++++++++-------------------------- public/js/winamp.js | 24 ++++++--------------- 4 files changed, 34 insertions(+), 60 deletions(-) diff --git a/modules/file-explorer.js b/modules/file-explorer.js index efcb652..ac93b33 100644 --- a/modules/file-explorer.js +++ b/modules/file-explorer.js @@ -5,7 +5,6 @@ const archiver = require('archiver'); const winston = require('winston'); const mkdirp = require('make-dir'); const m3uread = require('m3u8-reader'); -const path = require('path'); const masterFileTypesArray = ["mp3", "flac", "wav", "ogg", "aac", "m4a", "opus", "m3u"]; @@ -23,17 +22,17 @@ exports.setup = function(mstream, program) { } function getPathArray(pathString) { - return pathString.split(path.sep).filter(Boolean); + return pathString.split(fe.sep).filter(Boolean); } function getFileType(pathString) { - return path.extname(pathString).substr(1); + return fe.extname(pathString).substr(1); } function readPlaylistSongs(pathString) { return m3uread(fs.readFileSync(pathString)) .filter(function (item) { return typeof item === "string" }) - .map(function (item) { return item.replace(/\\/g, path.sep) }) // m3u path separated by \ + .map(function (item) { return item.replace(/\\/g, fe.sep) }) // m3u path separated by \ } function handleError(error, res) { @@ -84,19 +83,19 @@ exports.setup = function(mstream, program) { mstream.post('/fileplaylist/download', (req, res, next) => { try { const playlistPathInfo = getPathInfoOrThrow(req, req.body.path); - const playlistParentDir = path.dirname(req.body.path); + const playlistParentDir = fe.dirname(req.body.path); const songs = readPlaylistSongs(playlistPathInfo.fullPath); const archive = archiver('zip'); archive.on('error', function (err) { winston.error(`Download Error: ${err.message}`); res.status(500).json({ error: err.message }); }); - res.attachment(path.basename(req.body.path) + ".zip"); + res.attachment(fe.basename(req.body.path) + ".zip"); archive.pipe(res); for (let song of songs) { - const songPath = path.join(playlistParentDir, song); + const songPath = fe.join(playlistParentDir, song); const songPathInfo = getPathInfoOrThrow(req, songPath); - archive.file(songPathInfo.fullPath, { name: path.basename(song) }) + archive.file(songPathInfo.fullPath, { name: fe.basename(song) }) } archive.finalize(); } catch (error) { @@ -143,11 +142,11 @@ exports.setup = function(mstream, program) { mstream.post("/fileplaylist/load", function(req, res, next) { try { const playlistPathInfo = getPathInfoOrThrow(req, req.body.path); - const playlistParentDir = path.dirname(req.body.path); + const playlistParentDir = fe.dirname(req.body.path); const songs = readPlaylistSongs(playlistPathInfo.fullPath); res.json({ contents: songs.map(function (song) { - return {type: getFileType(song), name: path.basename(song), path: path.join(playlistParentDir, song)} + return {type: getFileType(song), name: fe.basename(song), path: fe.join(playlistParentDir, song)} }) }) } catch (error) { @@ -158,9 +157,9 @@ exports.setup = function(mstream, program) { mstream.post("/fileplaylist/loadpaths", function(req, res, next) { try { const playlistPathInfo = getPathInfoOrThrow(req, req.body.path); - const playlistParentDir = path.dirname(req.body.path); + const playlistParentDir = fe.dirname(req.body.path); const songs = readPlaylistSongs(playlistPathInfo.fullPath); - res.json(songs.map(function (song) { return path.join(playlistParentDir, song); })); + res.json(songs.map(function (song) { return fe.join(playlistParentDir, song); })); } catch (error) { handleError(error, res); } diff --git a/package.json b/package.json index e216b21..cc032c0 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "inquirer-select-directory": "^1.2.0", "jsonwebtoken": "^8.5.1", "lokijs": "^1.5.7", + "m3u8-reader": "^1.1.0", "make-dir": "^3.0.0", "mime-types": "^2.1.24", "music-metadata": "^4.8.2", diff --git a/public/js/mstream.js b/public/js/mstream.js index 34f0046..6bc7218 100755 --- a/public/js/mstream.js +++ b/public/js/mstream.js @@ -15,6 +15,14 @@ function escapeHtml (string) { }); } +function createFileplaylistHtml(dataDirectory) { + return '
' + dataDirectory + '
'; +} + +function createMusicfileHtml(fileLocation, title, titleClass) { + return '
' + title + '
'; +} + $(document).ready(function () { new ClipboardJS('.fed-copy-button'); @@ -358,7 +366,6 @@ $(document).ready(function () { ////////////////////////////// Global Variables // These vars track your position within the file explorer var fileExplorerArray = []; - var fileExplorerScrollPosition = []; // Stores an array of searchable objects var currentBrowsingList = []; @@ -414,28 +421,15 @@ $(document).ready(function () { // Reset file explorer vars fileExplorerArray = []; - fileExplorerScrollPosition = []; if (MSTREAMAPI.currentServer.vpaths && MSTREAMAPI.currentServer.vpaths.length === 1) { fileExplorerArray.push(MSTREAMAPI.currentServer.vpaths[0]); - fileExplorerScrollPosition.push(0); } //send this directory to be parsed and displayed senddir(null, fileExplorerArray); } - function setScrollPosition(scrollPosition) { - if (scrollPosition === false) { - var sp = $('#filelist').scrollTop(); - fileExplorerScrollPosition.push(sp); - $('#filelist').scrollTop(0); - } else if (scrollPosition === true) { - var sp = fileExplorerScrollPosition.pop(); - $('#filelist').scrollTop(sp); - } - } - // Load up the file explorer $('.get_file_explorer').on('click', loadFileExplorer); @@ -461,7 +455,6 @@ $(document).ready(function () { fileExplorerArray = newArray; printdir(response); - setScrollPosition(false); }); }); @@ -517,7 +510,6 @@ $(document).ready(function () { // Set any directory views // hand this data off to be printed on the page printdir(response); - setScrollPosition(scrollPosition); }); } @@ -537,11 +529,10 @@ $(document).ready(function () { filelist.push('
' + this.name + '
'); } else { if (this.type == 'm3u') { - filelist.push('
' + this.name + '
'); - } else if (this.artist != null || this.title != null) { - filelist.push('
' + this.artist + ' - ' + this.title + '
'); + filelist.push(createFileplaylistHtml(this.name)); } else { - filelist.push('
' + this.name + '
'); + const title = this.artist != null || this.title != null ? this.artist + ' - ' + this.title : this.name; + filelist.push(createMusicfileHtml(fileLocation, title, "item-text")); } } }); @@ -567,11 +558,6 @@ $(document).ready(function () { $('#search_folders').on('change keyup', function () { var searchVal = $(this).val(); - var path = ""; // Construct the directory string - for (var i = 0; i < fileExplorerArray.length; i++) { - path += fileExplorerArray[i] + "/"; - } - var filelist = []; // This causes an error in the playlist display $.each(currentBrowsingList, function () { @@ -602,12 +588,12 @@ $(document).ready(function () { } else { filelist.push('
' + this.metadata.artist + ' - ' + this.metadata.title + '
'); } + } else if (this.type == "m3u") { + filelist.push(createFileplaylistHtml(this.name)); } else { - if (this.artist != null || this.title != null) { - filelist.push('
' + this.artist + ' - ' + this.title + '
'); - } else { - filelist.push('
' + this.name + '
'); - } + const fileLocation = this.path || getFileExplorerPath(fileExplorerArray) + this.name; + const title = this.artist != null || this.title != null ? this.artist + ' - ' + this.title : this.name; + filelist.push(createMusicfileHtml(fileLocation, title, "title")); } } } diff --git a/public/js/winamp.js b/public/js/winamp.js index e3fd1fd..acaf4d4 100644 --- a/public/js/winamp.js +++ b/public/js/winamp.js @@ -166,7 +166,7 @@ $(document).ready(function () { this.pending = true; var that = this; MSTREAMAPI.login($('#login-username').val(), $('#login-password').val(), function (response, error) { - that.pending = false; + that.pending = false; if (error !== false) { // Alert the user iziToast.error({ @@ -181,10 +181,10 @@ $(document).ready(function () { if (typeof(Storage) !== "undefined") { localStorage.setItem("token", response.token); } - + // Reset Iframe $('#webamp-iframe').attr('src', '/public/webamp/webamp.html?token=' + response.token); - + // Add the token the URL calls MSTREAMAPI.updateCurrentServer($('#login-username').val(), response.token, response.vpaths) @@ -216,7 +216,7 @@ $(document).ready(function () { // set vPath MSTREAMAPI.currentServer.vpaths = response.vpaths; - // + // $('#webamp-iframe').attr('src', '/public/webamp/webamp.html?token=' + token); // Setup the file browser @@ -265,7 +265,6 @@ $(document).ready(function () { ////////////////////////////// Global Variables // These vars track your position within the file explorer var fileExplorerArray = []; - var fileExplorerScrollPosition = []; // Stores an array of searchable ojects var currentBrowsingList = []; @@ -311,11 +310,9 @@ $(document).ready(function () { // Reset file explorer vars fileExplorerArray = []; - fileExplorerScrollPosition = []; if (MSTREAMAPI.currentServer.vpaths && MSTREAMAPI.currentServer.vpaths.length === 1) { fileExplorerArray.push(MSTREAMAPI.currentServer.vpaths[0]); - fileExplorerScrollPosition.push(0); } //send this directory to be parsed and displayed @@ -393,15 +390,6 @@ $(document).ready(function () { // Set any directory views // hand this data off to be printed on the page printdir(response); - // Set scroll postion - if (scrollPosition === false) { - var sp = $('#filelist').scrollTop(); - fileExplorerScrollPosition.push(sp); - $('#filelist').scrollTop(0); - } else if (scrollPosition === true) { - var sp = fileExplorerScrollPosition.pop(); - $('#filelist').scrollTop(sp); - } }); } @@ -641,7 +629,7 @@ $(document).ready(function () { $('#filelist').html('
Server call failed
'); return boilerplateFailure(response, error); } - + // Add the playlist name to the modal $('#playlist_name').val(name); @@ -907,7 +895,7 @@ $(document).ready(function () { }); $('#filelist').html(albums); - // update linked list plugin + // update linked list plugin ll.update(); }); }