fix download plugin
also use config.js for config vars instead of leveldb
This commit is contained in:
parent
d6a685507a
commit
83ec17de20
12 changed files with 136 additions and 227 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,5 +1,6 @@
|
|||
/node_modules
|
||||
/groovebasin.db
|
||||
/config.js
|
||||
|
||||
# not shared with .npmignore
|
||||
/public/app.js
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
/node_modules
|
||||
/groovebasin.db
|
||||
/config.js
|
||||
|
||||
# not shared with .gitignore
|
||||
|
|
|
|||
5
TODO
5
TODO
|
|
@ -1,10 +1,9 @@
|
|||
High priority:
|
||||
* (andy) fix broken plugin - delete
|
||||
* (andy) fix broken plugin - download
|
||||
|
||||
Before merging into master:
|
||||
* fix broken plugin - upload
|
||||
- while you're at it consider ditching qq fileuploader
|
||||
* fix broken plugin - download
|
||||
* fix broken plugin - lastfm
|
||||
* fix broken plugin - dynamicmode
|
||||
* persist current playlist
|
||||
|
|
@ -22,3 +21,5 @@ Eventually:
|
|||
* ditch qq fileuploader
|
||||
* extract the skewed random song selection into a separate, tested module
|
||||
* player: finer-grained updates. 'playlistUpdate' shouldn't be the only kind of update
|
||||
- also should utilize the player's 'delete' and 'update' events for library changes
|
||||
* player should notice when you delete a file while it is not running.
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
var levelup = require('level');
|
||||
|
||||
module.exports = function (dbPath) {
|
||||
dbPath = dbPath || "./groovebasin.db";
|
||||
return levelup(dbPath);
|
||||
};
|
||||
|
|
@ -13,28 +13,38 @@ var requireIndex = require('requireindex');
|
|||
var plugins = requireIndex(path.join(__dirname, 'plugins'));
|
||||
var Player = require('./player');
|
||||
var PlayerServer = require('./player_server');
|
||||
var getDb = require('./db');
|
||||
var levelup = require('level');
|
||||
|
||||
module.exports = GrooveBasin;
|
||||
|
||||
var CONFIG_KEY_PREFIX = "Config.";
|
||||
|
||||
var defaultConfig = {
|
||||
host: '0.0.0.0',
|
||||
port: 16242,
|
||||
dbPath: "groovebasin.db",
|
||||
musicDirectory: path.join(osenv.home(), "music"),
|
||||
permissions: {},
|
||||
defaultPermissions: {
|
||||
read: true,
|
||||
add: true,
|
||||
control: true,
|
||||
},
|
||||
};
|
||||
|
||||
defaultConfig.permissions[genPassword()] = {
|
||||
admin: true,
|
||||
read: true,
|
||||
add: true,
|
||||
control: true,
|
||||
};
|
||||
|
||||
util.inherits(GrooveBasin, EventEmitter);
|
||||
function GrooveBasin() {
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.app = express();
|
||||
|
||||
// defaults until we load the real values from the db
|
||||
this.config = {
|
||||
musicDirectory: path.join(osenv.home(), "music"),
|
||||
permissions: {},
|
||||
defaultPermissions: {
|
||||
read: true,
|
||||
add: true,
|
||||
control: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
GrooveBasin.prototype.initConfigVar = function(name, defaultValue) {
|
||||
|
|
@ -42,20 +52,58 @@ GrooveBasin.prototype.initConfigVar = function(name, defaultValue) {
|
|||
this[name] = defaultValue;
|
||||
};
|
||||
|
||||
GrooveBasin.prototype.start = function(options) {
|
||||
GrooveBasin.prototype.loadConfig = function(cb) {
|
||||
var self = this;
|
||||
self.httpHost = options.host || "0.0.0.0";
|
||||
self.httpPort = options.port || 16242;
|
||||
self.db = getDb(options.dbPath);
|
||||
|
||||
self.app.use(express.static(path.join(__dirname, '../public')));
|
||||
self.app.use(express.static(path.join(__dirname, '../src/public')));
|
||||
|
||||
self.restoreState(function(err) {
|
||||
var pathToConfig = "config.js";
|
||||
fs.readFile(pathToConfig, {encoding: 'utf8'}, function(err, contents) {
|
||||
var anythingAdded = false;
|
||||
var config;
|
||||
if (err) {
|
||||
console.error("unable to restore state:", err.stack);
|
||||
if (err.code === 'ENOENT') {
|
||||
anythingAdded = true;
|
||||
self.config = defaultConfig;
|
||||
console.warn("No config.js found; writing default.");
|
||||
} else {
|
||||
return cb(err);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
self.config = JSON.parse(contents);
|
||||
} catch (err) {
|
||||
cb(err);
|
||||
}
|
||||
}
|
||||
// this ensures that even old files get new config values when we add them
|
||||
for (var key in defaultConfig) {
|
||||
if (self.config[key] === undefined) {
|
||||
anythingAdded = true;
|
||||
self.config[key] = defaultConfig[key];
|
||||
}
|
||||
}
|
||||
if (anythingAdded) {
|
||||
fs.writeFile(pathToConfig, JSON.stringify(self.config, null, 4), cb);
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
GrooveBasin.prototype.start = function() {
|
||||
var self = this;
|
||||
|
||||
self.loadConfig(function(err) {
|
||||
if (err) {
|
||||
console.error("Error reading config:", err.stack);
|
||||
return;
|
||||
}
|
||||
|
||||
self.httpHost = self.config.host;
|
||||
self.httpPort = self.config.port;
|
||||
self.db = levelup(self.config.dbPath);
|
||||
|
||||
self.app.use(express.static(path.join(__dirname, '../public')));
|
||||
self.app.use(express.static(path.join(__dirname, '../src/public')));
|
||||
|
||||
self.player = new Player(self.db, self.config.musicDirectory);
|
||||
self.player.initialize(function(err) {
|
||||
if (err) {
|
||||
|
|
@ -86,6 +134,7 @@ GrooveBasin.prototype.start = function(options) {
|
|||
function authenticate(password) {
|
||||
return self.config.permissions[password];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GrooveBasin.prototype.restoreState = function(cb) {
|
||||
|
|
@ -138,3 +187,6 @@ GrooveBasin.prototype.startServer = function() {
|
|||
}
|
||||
}
|
||||
|
||||
function genPassword() {
|
||||
return Math.random().toString();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
var groove = require('groove');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var Pend = require('pend');
|
||||
var chokidar = require('chokidar');
|
||||
|
|
@ -109,21 +110,16 @@ Player.prototype.initialize = function(cb) {
|
|||
|
||||
self.watcher.on('add', onAddOrChange);
|
||||
self.watcher.on('change', onAddOrChange);
|
||||
self.watcher.on('unlink', removeFile);
|
||||
self.watcher.on('unlink', onFileMissing);
|
||||
|
||||
self.watcher.on('error', function(err) {
|
||||
console.error("library watching error:", err.stack);
|
||||
});
|
||||
}
|
||||
|
||||
function removeFile(file) {
|
||||
self.db.del(file, function(err) {
|
||||
if (err) {
|
||||
console.error("Error deleting", file, err.stack);
|
||||
return;
|
||||
}
|
||||
self.emit('delete', file);
|
||||
});
|
||||
function onFileMissing(fullPath) {
|
||||
var relPath = path.relative(self.musicDirectory, fullPath);
|
||||
self.delDbEntry(relPath);
|
||||
}
|
||||
|
||||
function onAddOrChange(fullPath, stat) {
|
||||
|
|
@ -168,6 +164,28 @@ Player.prototype.initialize = function(cb) {
|
|||
}
|
||||
};
|
||||
|
||||
Player.prototype.deleteFile = function(relPath) {
|
||||
var self = this;
|
||||
var fullPath = path.join(self.musicDirectory, relPath);
|
||||
fs.unlink(fullPath, function(err) {
|
||||
if (err) {
|
||||
console.error("Error deleting", relPath, err.stack);
|
||||
}
|
||||
});
|
||||
self.delDbEntry(relPath);
|
||||
};
|
||||
|
||||
Player.prototype.delDbEntry = function(relPath) {
|
||||
var self = this;
|
||||
delete self.allDbFiles[relPath];
|
||||
self.emit('delete', relPath);
|
||||
self.db.del(relPath, function(err) {
|
||||
if (err) {
|
||||
console.error("Error deleting db entry", relPath, err.stack);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Player.prototype.setVolume = function(value) {
|
||||
value = Math.min(1.0, value);
|
||||
value = Math.max(0.0, value);
|
||||
|
|
|
|||
|
|
@ -5,23 +5,7 @@ module.exports = Delete;
|
|||
|
||||
function Delete(gb) {
|
||||
this.gb = gb;
|
||||
this.is_enabled = true;
|
||||
setup(this);
|
||||
}
|
||||
|
||||
function setup(self) {
|
||||
self.gb.on('aboutToSaveState', function(state) {
|
||||
state.status.delete_enabled = self.is_enabled;
|
||||
});
|
||||
self.gb.on('socketConnect', onSocketConnection.bind(self));
|
||||
self.gb.on('stateRestored', function(state) {
|
||||
self.music_directory = state.musicDirectory;
|
||||
if (self.music_directory == null) {
|
||||
self.is_enabled = false;
|
||||
console.warn("No music directory set. Delete disabled.");
|
||||
return;
|
||||
}
|
||||
});
|
||||
this.gb.on('socketConnect', onSocketConnection.bind(this));
|
||||
}
|
||||
|
||||
function onSocketConnection(client) {
|
||||
|
|
@ -32,21 +16,8 @@ function onSocketConnection(client) {
|
|||
return;
|
||||
}
|
||||
var files = JSON.parse(data);
|
||||
var file = null;
|
||||
function next(err){
|
||||
var file;
|
||||
if (err) {
|
||||
console.error("deleting " + file + ": " + err.stack);
|
||||
} else if (file != null) {
|
||||
console.info("deleted " + file);
|
||||
}
|
||||
if ((file = files.shift()) == null) {
|
||||
self.gb.rescanLibrary();
|
||||
} else {
|
||||
fs.unlink(path.join(self.music_directory, file), next);
|
||||
// TODO remove from library?
|
||||
}
|
||||
}
|
||||
next();
|
||||
files.forEach(function(file) {
|
||||
self.gb.player.deleteFile(file);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,4 @@ gb.on('listening', function() {
|
|||
process.on('message', function(message){
|
||||
if (message === 'shutdown') process.exit(0);
|
||||
});
|
||||
gb.start({
|
||||
dbPath: process.env.DB_PATH,
|
||||
host: process.env.HOST,
|
||||
port: process.env.PORT,
|
||||
});
|
||||
gb.start();
|
||||
|
|
|
|||
104
lib/state.js
104
lib/state.js
|
|
@ -1,104 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/* this command line utility displays or updates information from the db */
|
||||
|
||||
var getDb = require('./db');
|
||||
var db = getDb(process.env.DB_PATH);
|
||||
|
||||
var args = process.argv.slice(2);
|
||||
|
||||
if (args.length === 0) {
|
||||
dump();
|
||||
} else {
|
||||
processArgs();
|
||||
}
|
||||
|
||||
function processArgs() {
|
||||
var openThings = 0;
|
||||
var putFlag = false;
|
||||
var putKey = null;
|
||||
var delFlag = false;
|
||||
var getFlag = false;
|
||||
process.argv.slice(2).forEach(function(arg) {
|
||||
if (getFlag) {
|
||||
get(arg);
|
||||
getFlag = false;
|
||||
} else if (delFlag) {
|
||||
del(arg);
|
||||
delFlag = false;
|
||||
} else if (putKey != null) {
|
||||
put(putKey, arg);
|
||||
putFlag = false;
|
||||
} else if (putFlag) {
|
||||
putKey = arg;
|
||||
} else if (arg === '--get') {
|
||||
getFlag = true;
|
||||
} else if (arg === '--put') {
|
||||
putFlag = true;
|
||||
} else if (arg === '--del') {
|
||||
delFlag = true;
|
||||
} else {
|
||||
console.error("Unexpected argument:", arg);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
function get(key) {
|
||||
openAThing();
|
||||
db.get(key, function(err, val) {
|
||||
closeAThing();
|
||||
if (err) {
|
||||
console.error("Error getting", key, err.stack);
|
||||
return;
|
||||
}
|
||||
console.log(key, "=", val);
|
||||
});
|
||||
}
|
||||
function put(key, val) {
|
||||
openAThing();
|
||||
db.put(key, val, function(err) {
|
||||
closeAThing();
|
||||
if (err) {
|
||||
console.error("Error putting", key, err.stack);
|
||||
return;
|
||||
}
|
||||
console.log("put", key, "=", val);
|
||||
});
|
||||
}
|
||||
function del(key) {
|
||||
openAThing();
|
||||
db.del(key, function(err, val) {
|
||||
closeAThing();
|
||||
if (err) {
|
||||
console.error("Error deleting", key, err.stack);
|
||||
return;
|
||||
}
|
||||
console.log("del", key);
|
||||
});
|
||||
}
|
||||
|
||||
function openAThing() {
|
||||
openThings += 1;
|
||||
}
|
||||
|
||||
function closeAThing() {
|
||||
openThings -= 1;
|
||||
if (openThings === 0) {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dump() {
|
||||
var stream = db.createReadStream();
|
||||
stream.on('data', function(data) {
|
||||
console.log(data.key, "=", data.value);
|
||||
});
|
||||
stream.on('error', function(err) {
|
||||
console.error(err.stack);
|
||||
});
|
||||
stream.on('close', function() {
|
||||
db.close();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -546,15 +546,13 @@ function getDragPosition(x, y){
|
|||
}
|
||||
return result;
|
||||
}
|
||||
function renderSettings(){
|
||||
var api_key, context;
|
||||
if ((api_key = server_status != null ? server_status.lastfm_api_key : void 8) == null) {
|
||||
return;
|
||||
}
|
||||
context = {
|
||||
|
||||
function renderSettings() {
|
||||
var apiKey = ""; // TODO fix this when lastfm plugin is fixed
|
||||
var context = {
|
||||
lastfm: {
|
||||
auth_url: "http://www.last.fm/api/auth/?api_key=" +
|
||||
encodeURIComponent(api_key) + "&cb=" +
|
||||
encodeURIComponent(apiKey) + "&cb=" +
|
||||
encodeURIComponent(location.protocol + "//" + location.host + "/"),
|
||||
username: localState.lastfm.username,
|
||||
session_key: localState.lastfm.session_key,
|
||||
|
|
@ -578,6 +576,7 @@ function renderSettings(){
|
|||
$settings.find('.auth-clear').button();
|
||||
$settings.find('#auth-password').val(settings_ui.auth.password);
|
||||
}
|
||||
|
||||
function scrollChatWindowToBottom(){
|
||||
$chatList.scrollTop(1000000);
|
||||
}
|
||||
|
|
@ -636,12 +635,10 @@ function renderPlaylistButtons(){
|
|||
}
|
||||
}
|
||||
function renderPlaylist(){
|
||||
var context, scroll_top;
|
||||
context = {
|
||||
var context = {
|
||||
playlist: mpd.playlist.item_list,
|
||||
server_status: server_status
|
||||
};
|
||||
scroll_top = $playlist_items.scrollTop();
|
||||
var scroll_top = $playlist_items.scrollTop();
|
||||
$playlist_items.html(Handlebars.templates.playlist(context));
|
||||
refreshSelection();
|
||||
labelPlaylistItems();
|
||||
|
|
@ -1313,12 +1310,10 @@ function queueSelection(event){
|
|||
}
|
||||
return false;
|
||||
}
|
||||
function sendAuth(){
|
||||
var pass;
|
||||
pass = localState.authPassword;
|
||||
if (pass == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
function sendAuth() {
|
||||
var pass = localState.authPassword;
|
||||
if (!pass) return;
|
||||
mpd.authenticate(pass, function(err){
|
||||
if (err) {
|
||||
localState.authPassword = null;
|
||||
|
|
@ -1327,10 +1322,10 @@ function sendAuth(){
|
|||
renderSettings();
|
||||
});
|
||||
}
|
||||
|
||||
function settingsAuthSave(){
|
||||
var $text_box;
|
||||
settings_ui.auth.show_edit = false;
|
||||
$text_box = $('#auth-password');
|
||||
var $text_box = $('#auth-password');
|
||||
localState.authPassword = $text_box.val();
|
||||
saveLocalState();
|
||||
renderSettings();
|
||||
|
|
@ -1341,10 +1336,9 @@ function settingsAuthCancel(){
|
|||
renderSettings();
|
||||
}
|
||||
function performDrag(event, callbacks){
|
||||
var start_drag_x, start_drag_y;
|
||||
abortDrag();
|
||||
start_drag_x = event.pageX;
|
||||
start_drag_y = event.pageY;
|
||||
var start_drag_x = event.pageX;
|
||||
var start_drag_y = event.pageY;
|
||||
abortDrag = function(){
|
||||
$document.off('mousemove', onDragMove).off('mouseup', onDragEnd);
|
||||
if (started_drag) {
|
||||
|
|
@ -1502,7 +1496,7 @@ function setUpPlaylistUi(){
|
|||
refreshSelection();
|
||||
}
|
||||
context = {
|
||||
status: server_status,
|
||||
downloadEnabled: false, // TODO this will change when download plugin is fixed
|
||||
permissions: permissions
|
||||
};
|
||||
if (selection.isMulti()) {
|
||||
|
|
@ -1957,7 +1951,7 @@ function genericTreeUi($elem, options){
|
|||
refreshSelection();
|
||||
}
|
||||
context = {
|
||||
status: server_status,
|
||||
downloadEnabled: false, // TODO change this when download plugin is fixed
|
||||
permissions: permissions
|
||||
};
|
||||
if (selection.isMulti()) {
|
||||
|
|
@ -2123,13 +2117,6 @@ $document.ready(function(){
|
|||
chatState = data;
|
||||
renderChat();
|
||||
});
|
||||
socket.on('Status', function(data){
|
||||
server_status = JSON.parse(data.toString());
|
||||
renderPlaylistButtons();
|
||||
labelPlaylistItems();
|
||||
renderSettings();
|
||||
window._debug_server_status = server_status;
|
||||
});
|
||||
mpd = new PlayerClient(socket);
|
||||
mpd.on('libraryupdate', renderLibrary);
|
||||
mpd.on('playlistupdate', renderPlaylist);
|
||||
|
|
|
|||
|
|
@ -4,18 +4,14 @@
|
|||
<li><a href="#" class="queue-random hoverable">Queue in Random Order</a></li>
|
||||
<li><a href="#" class="queue-next-random hoverable">Queue Next in Random Order</a></li>
|
||||
<li>
|
||||
{{#if status.delete_enabled}}
|
||||
{{#if permissions.admin}}
|
||||
<a href="#" class="delete hoverable">Delete</a>
|
||||
{{else}}
|
||||
<span title="Delete is disabled: insufficient privileges. See Settings.">Delete</span>
|
||||
{{/if}}
|
||||
{{#if permissions.admin}}
|
||||
<a href="#" class="delete hoverable">Delete</a>
|
||||
{{else}}
|
||||
<span title="Delete is disabled due to invalid server configuration.">Delete</span>
|
||||
<span title="Delete is disabled: insufficient privileges. See Settings.">Delete</span>
|
||||
{{/if}}
|
||||
</li>
|
||||
<li>
|
||||
{{#if status.download_enabled}}
|
||||
{{#if downloadEnabled}}
|
||||
{{#if track}}
|
||||
<a href="library/{{track.file}}" class="download hoverable" target="_blank">Download</a>
|
||||
{{/if}}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,14 @@
|
|||
<ul id="menu" class="ui-widget-content ui-corner-all">
|
||||
<li><a href="#" class="remove hoverable">Remove</a></li>
|
||||
<li>
|
||||
{{#if status.delete_enabled}}
|
||||
{{#if permissions.admin}}
|
||||
<a href="#" class="delete hoverable">Delete From Library</a>
|
||||
{{else}}
|
||||
<span title="Delete is disabled: insufficient privileges. See Settings.">Delete From Library</span>
|
||||
{{/if}}
|
||||
{{#if permissions.admin}}
|
||||
<a href="#" class="delete hoverable">Delete From Library</a>
|
||||
{{else}}
|
||||
<span title="Delete is disabled due to invalid server configuration.">Delete From Library</span>
|
||||
<span title="Delete is disabled: insufficient privileges. See Settings.">Delete From Library</span>
|
||||
{{/if}}
|
||||
</li>
|
||||
<li>
|
||||
{{#if status.download_enabled}}
|
||||
{{#if downloadEnabled}}
|
||||
{{#if item}}
|
||||
<a href="library/{{item.track.file}}" class="download hoverable" target="_blank">Download</a>
|
||||
{{/if}}
|
||||
|
|
|
|||
Loading…
Reference in a new issue