persist playlist on server restart. closes #98

This commit is contained in:
Andrew Kelley 2014-03-04 02:27:31 -05:00
parent 6068a19f3e
commit ea77be50b5

View file

@ -27,6 +27,7 @@ var cpuCount = require('os').cpus().length;
var PLAYER_KEY_PREFIX = "Player.";
var LIBRARY_KEY_PREFIX = "Library.";
var LIBRARY_DIR_PREFIX = "LibraryDir.";
var PLAYLIST_KEY_PREFIX = "Playlist.";
// these are the ones we store in the DB, not the ones we send to the web
var DB_FILE_PROPS = [
@ -201,14 +202,14 @@ Player.prototype.initialize = function(cb) {
var posMs = playHead.pos * 1000;
self.trackStartDate = new Date(nowMs - posMs);
self.currentTrack = self.grooveItems[playHead.item.id];
self.emit('currentTrack');
playlistChanged(self);
self.emit('currentTrack');
} else if (!decodeHead.item) {
// both play head and decode head are null. end of playlist.
console.log("end of playlist");
self.currentTrack = null;
self.emit('currentTrack');
playlistChanged(self);
self.emit('currentTrack');
}
}
}
@ -219,12 +220,36 @@ Player.prototype.initialize = function(cb) {
pend.go(cacheAllDb);
pend.go(cacheAllDirs);
pend.go(cacheAllOptions);
pend.go(cacheAllPlaylist);
pend.wait(function(err) {
if (err) {
cb(err);
if (err) return cb(err);
playlistChanged(self);
lazyReplayGainScanPlaylist(self);
self.requestUpdateDb();
cb();
});
}
function cacheAllPlaylist(cb) {
var stream = self.db.createReadStream({
start: PLAYLIST_KEY_PREFIX,
});
stream.on('data', function(data) {
if (data.key.indexOf(PLAYLIST_KEY_PREFIX) !== 0) {
stream.removeAllListeners();
stream.destroy();
cb();
return;
}
self.requestUpdateDb();
var plEntry = JSON.parse(data.value);
self.playlist[plEntry.id] = plEntry;
});
stream.on('error', function(err) {
stream.removeAllListeners();
stream.destroy();
cb(err);
});
stream.on('close', function() {
cb();
});
}
@ -379,14 +404,15 @@ Player.prototype.refreshFilesIndex = function(dir, cb) {
var i;
for (var scannedDirName in scannedDirs) {
var dirEntry = scannedDirs[scannedDirName];
self.persistDirEntry(dirEntry);
var deletedFiles = [];
for (baseName in dirEntry.entries) {
id = dirEntry.entries[baseName];
if (id !== thisScanId) deletedFiles.push(baseName);
}
for (i = 0; i < deletedFiles.length; i += 1) {
onFileMissing(dirEntry, deletedFiles[i]);
baseName = deletedFiles[i];
delete dirEntry.entries[baseName];
onFileMissing(dirEntry, baseName);
}
var deletedDirs = [];
@ -395,8 +421,12 @@ Player.prototype.refreshFilesIndex = function(dir, cb) {
if (id !== thisScanId) deletedDirs.push(baseName);
}
for (i = 0; i < deletedDirs.length; i += 1) {
onDirMissing(dirEntry, deletedDirs[i]);
baseName = deletedDirs[i];
delete dirEntry.dirEntries[baseName];
onDirMissing(dirEntry, baseName);
}
self.persistDirEntry(dirEntry);
}
cb();
});
@ -665,6 +695,16 @@ Player.prototype.persist = function(dbFile, cb) {
}
};
Player.prototype.persistPlaylistItem = function(item, cb) {
this.db.put(PLAYLIST_KEY_PREFIX + item.id, serializePlaylistItem(item), cb || logIfError);
function logIfError(err) {
if (err) {
console.error("unable to persist playlist item:", item, err.stack);
}
}
};
Player.prototype.persistOption = function(name, value, cb) {
this.db.put(PLAYER_KEY_PREFIX + name, JSON.stringify(value), cb || logIfError);
function logIfError(err) {
@ -755,23 +795,14 @@ Player.prototype.addItems = function(items, tagAsRandom) {
deleted: false,
};
self.playlist[id] = playlistItem;
self.persistPlaylistItem(playlistItem);
}
playlistChanged(self);
lazyReplayGainScanPlaylist(self);
}
Player.prototype.clearPlaylist = function() {
for (var id in this.playlist) {
var track = this.playlist[id];
if (track.grooveFile) closeFile(track.grooveFile);
// we set this so that any callbacks that return which were trying to
// set the groveItem can check if the item got deleted
track.deleted = true;
}
this.playlist = {};
this.currentTrack = null;
this.emit('currentTrack');
playlistChanged(this);
this.removePlaylistItems(Object.keys(this.playlist));
}
Player.prototype.shufflePlaylist = function() {
@ -787,10 +818,34 @@ Player.prototype.shufflePlaylist = function() {
}
Player.prototype.removePlaylistItems = function(ids) {
ids.forEach(function(id) {
var delCmds = [];
var currentTrackChanged = false;
for (var i = 0; i < ids.length; i += 1) {
var id = ids[i];
delCmds.push({type: 'del', key: PLAYLIST_KEY_PREFIX + id});
var item = this.playlist[id];
if (item.grooveFile) closeFile(item.grooveFile);
// we set this so that any callbacks that return which were trying to
// set the grooveItem can check if the item got deleted
item.deleted = true;
if (item === this.currentTrack) {
this.currentTrack = null;
currentTrackChanged = true;
}
delete this.playlist[id];
}.bind(this));
}
if (delCmds.length > 0) this.db.batch(delCmds, logIfError);
playlistChanged(this);
if (currentTrackChanged) this.emit('currentTrack');
function logIfError(err) {
if (err) {
console.error("Error deleting playlist entries from db:", err.stack);
}
}
}
// items looks like {id: {sortKey}}
@ -799,6 +854,7 @@ Player.prototype.movePlaylistItems = function(items) {
var track = this.playlist[id];
if (track == null) continue; // race conditions, etc.
track.sortKey = items[id].sortKey;
this.persistPlaylistItem(track);
}
playlistChanged(this);
}
@ -829,6 +885,7 @@ Player.prototype.moveIdsToPos = function(ids, toPos) {
var thisSortKey = keese(prevSortKey, nextSortKey);
prevSortKey = thisSortKey;
track.sortKey = thisSortKey;
this.persistPlaylistItem(track);
}
playlistChanged(this);
}
@ -838,8 +895,8 @@ Player.prototype.pause = function() {
this.isPlaying = false;
this.pausedTime = (new Date() - this.trackStartDate) / 1000;
this.groovePlaylist.pause();
this.emit('currentTrack');
playlistChanged(this);
this.emit('currentTrack');
}
Player.prototype.play = function() {
@ -850,8 +907,8 @@ Player.prototype.play = function() {
}
this.groovePlaylist.play();
this.isPlaying = true;
this.emit('currentTrack');
playlistChanged(this);
this.emit('currentTrack');
}
// This function should be avoided in favor of seek. Note that it is called by
@ -861,8 +918,8 @@ Player.prototype.seekToIndex = function(index, pos) {
this.isPlaying = true;
this.groovePlaylist.play();
this.seekRequestPos = pos;
this.emit('currentTrack');
playlistChanged(this);
this.emit('currentTrack');
};
Player.prototype.seek = function(id, pos) {
@ -870,8 +927,8 @@ Player.prototype.seek = function(id, pos) {
this.isPlaying = true;
this.groovePlaylist.play();
this.seekRequestPos = pos;
this.emit('currentTrack');
playlistChanged(this);
this.emit('currentTrack');
}
Player.prototype.next = function() {
@ -900,8 +957,8 @@ Player.prototype.setRepeat = function(value) {
if (value === this.repeat) return;
this.repeat = value;
this.persistOption('repeat', this.repeat);
this.emit('repeatUpdate');
playlistChanged(this);
this.emit('repeatUpdate');
};
Player.prototype.setDynamicModeOn = function(value) {
@ -1209,6 +1266,7 @@ function lazyReplayGainScanPlaylist(self) {
var albumGain = {};
self.tracksInOrder.forEach(function(track) {
var dbFile = self.libraryIndex.trackTable[track.key];
if (!dbFile) return;
var albumKey = self.libraryIndex.getAlbumKey(dbFile);
var needScan = dbFile.replayGainAlbumGain == null ||
dbFile.replayGainTrackGain == null ||
@ -1409,6 +1467,15 @@ function deserializeFileData(dataStr) {
return JSON.parse(dataStr);
}
function serializePlaylistItem(item) {
return JSON.stringify({
id: item.id,
key: item.key,
sortKey: item.sortKey,
isRandom: item.isRandom,
});
}
function trackWithoutIndex(props, dbFile) {
var out = {};
props.forEach(function(propName) {