fix download plugin

also use config.js for config vars instead of leveldb
This commit is contained in:
Andrew Kelley 2013-09-26 04:21:31 -04:00
parent d6a685507a
commit 83ec17de20
12 changed files with 136 additions and 227 deletions

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
/node_modules
/groovebasin.db
/config.js
# not shared with .npmignore
/public/app.js

View file

@ -1,4 +1,5 @@
/node_modules
/groovebasin.db
/config.js
# not shared with .gitignore

5
TODO
View file

@ -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.

View file

@ -1,6 +0,0 @@
var levelup = require('level');
module.exports = function (dbPath) {
dbPath = dbPath || "./groovebasin.db";
return levelup(dbPath);
};

View file

@ -13,20 +13,16 @@ 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.";
util.inherits(GrooveBasin, EventEmitter);
function GrooveBasin() {
EventEmitter.call(this);
this.app = express();
// defaults until we load the real values from the db
this.config = {
var defaultConfig = {
host: '0.0.0.0',
port: 16242,
dbPath: "groovebasin.db",
musicDirectory: path.join(osenv.home(), "music"),
permissions: {},
defaultPermissions: {
@ -34,7 +30,21 @@ function GrooveBasin() {
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();
}
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);
var pathToConfig = "config.js";
fs.readFile(pathToConfig, {encoding: 'utf8'}, function(err, contents) {
var anythingAdded = false;
var config;
if (err) {
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.restoreState(function(err) {
if (err) {
console.error("unable to restore state:", err.stack);
return;
}
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();
}

View file

@ -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);

View file

@ -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);
});
});
}

View 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();

View file

@ -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();
});
}

View file

@ -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);

View file

@ -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}}
{{else}}
<span title="Delete is disabled due to invalid server configuration.">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}}

View file

@ -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}}
{{else}}
<span title="Delete is disabled due to invalid server configuration.">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}}