detect file deletions on startup. closes #116
This commit is contained in:
parent
fbc6386960
commit
0ca3c286f2
1 changed files with 102 additions and 48 deletions
150
lib/player.js
150
lib/player.js
|
|
@ -22,6 +22,7 @@ groove.setLogging(groove.LOG_WARNING);
|
|||
var cpuCount = require('os').cpus().length;
|
||||
|
||||
var LIBRARY_KEY_PREFIX = "Library.";
|
||||
var LIBRARY_DIR_PREFIX = "LibraryDir.";
|
||||
|
||||
var DB_FILE_PROPS = [
|
||||
'key', 'name', 'artistName', 'albumArtistName',
|
||||
|
|
@ -202,17 +203,43 @@ Player.prototype.initialize = function(cb) {
|
|||
}
|
||||
|
||||
function initLibrary(cb) {
|
||||
cacheAllDb(function(err) {
|
||||
var pend = new Pend();
|
||||
pend.go(cacheAllDb);
|
||||
pend.go(cacheAllDirs);
|
||||
pend.wait(function(err) {
|
||||
if (err) {
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
|
||||
self.requestUpdateDb();
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
function cacheAllDirs(cb) {
|
||||
var stream = self.db.createReadStream({
|
||||
start: LIBRARY_DIR_PREFIX,
|
||||
});
|
||||
stream.on('data', function(data) {
|
||||
if (data.key.indexOf(LIBRARY_DIR_PREFIX) !== 0) {
|
||||
stream.removeAllListeners();
|
||||
stream.destroy();
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
var dirEntry = JSON.parse(data.value);
|
||||
self.dirs[dirEntry.dirName] = dirEntry;
|
||||
});
|
||||
stream.on('error', function(err) {
|
||||
stream.removeAllListeners();
|
||||
stream.destroy();
|
||||
cb(err);
|
||||
});
|
||||
stream.on('close', function() {
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
function cacheAllDb(cb) {
|
||||
var scrubCmds = [];
|
||||
var stream = self.db.createReadStream({
|
||||
|
|
@ -265,13 +292,13 @@ Player.prototype.refreshFilesIndex = function(dir, cb) {
|
|||
var scannedDirs = {};
|
||||
walker.on('directory', function(fullDirPath, stat, stop) {
|
||||
var dirName = path.relative(self.musicDirectory, fullDirPath);
|
||||
var dirEntry = getOrCreateDir(dirName, stat);
|
||||
var dirEntry = self.getOrCreateDir(dirName, stat);
|
||||
scannedDirs[dirName] = dirEntry;
|
||||
if (fullDirPath === dir) return; // ignore root search path
|
||||
var baseName = path.basename(dirName);
|
||||
var parentDirName = path.dirname(dirName);
|
||||
if (parentDirName === '.') parentDirName = '';
|
||||
var parentDirEntry = getOrCreateDir(parentDirName);
|
||||
var parentDirEntry = self.getOrCreateDir(parentDirName);
|
||||
parentDirEntry.dirEntries[baseName] = thisScanId;
|
||||
scannedDirs[parentDirName] = parentDirEntry;
|
||||
});
|
||||
|
|
@ -281,7 +308,7 @@ Player.prototype.refreshFilesIndex = function(dir, cb) {
|
|||
if (dirName === '.') dirName = '';
|
||||
var baseName = path.basename(relPath);
|
||||
if (isFileIgnored(baseName)) return;
|
||||
var dirEntry = getOrCreateDir(dirName);
|
||||
var dirEntry = self.getOrCreateDir(dirName);
|
||||
dirEntry.entries[baseName] = thisScanId;
|
||||
onAddOrChange(relPath, stat);
|
||||
});
|
||||
|
|
@ -294,6 +321,7 @@ 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];
|
||||
|
|
@ -315,47 +343,6 @@ Player.prototype.refreshFilesIndex = function(dir, cb) {
|
|||
cb();
|
||||
});
|
||||
|
||||
function getOrCreateDir(dirName, stat) {
|
||||
var dirEntry = self.dirs[dirName];
|
||||
var fullDirPath = path.join(self.musicDirectory, dirName);
|
||||
var changeTriggered = null;
|
||||
|
||||
if (!dirEntry) {
|
||||
var watcher;
|
||||
console.log("watch", fullDirPath);
|
||||
try {
|
||||
watcher = fs.watch(fullDirPath, onChange);
|
||||
watcher.on('error', onWatchError);
|
||||
} catch (err) {
|
||||
console.error("Unable to fs.watch:", err.stack);
|
||||
watcher = null;
|
||||
}
|
||||
dirEntry = self.dirs[dirName] = {
|
||||
dirName: dirName,
|
||||
entries: {},
|
||||
dirEntries: {},
|
||||
watcher: watcher,
|
||||
mtime: stat && stat.mtime,
|
||||
};
|
||||
} else if (stat && dirEntry.mtime !== stat.mtime) {
|
||||
dirEntry.mtime = stat.mtime;
|
||||
}
|
||||
return dirEntry;
|
||||
|
||||
function onChange(eventName) {
|
||||
if (changeTriggered) clearTimeout(changeTriggered);
|
||||
changeTriggered = setTimeout(function() {
|
||||
changeTriggered = null;
|
||||
console.log("dir updated:", dirName);
|
||||
self.dirScanQueue.add(fullDirPath, fullDirPath);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function onWatchError(err) {
|
||||
console.error("watch error:", err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
function onDirMissing(parentDirEntry, baseName) {
|
||||
var dirName = path.join(parentDirEntry.dirName, baseName);
|
||||
console.log("directory deleted:", dirName);
|
||||
|
|
@ -395,6 +382,54 @@ Player.prototype.refreshFilesIndex = function(dir, cb) {
|
|||
}
|
||||
}
|
||||
|
||||
Player.prototype.watchDirEntry = function(dirEntry) {
|
||||
var self = this;
|
||||
var changeTriggered = null;
|
||||
var fullDirPath = path.join(self.musicDirectory, dirEntry.dirName);
|
||||
var watcher;
|
||||
console.log("watch", fullDirPath);
|
||||
try {
|
||||
watcher = fs.watch(fullDirPath, onChange);
|
||||
watcher.on('error', onWatchError);
|
||||
} catch (err) {
|
||||
console.error("Unable to fs.watch:", err.stack);
|
||||
watcher = null;
|
||||
}
|
||||
dirEntry.watcher = watcher;
|
||||
|
||||
function onChange(eventName) {
|
||||
if (changeTriggered) clearTimeout(changeTriggered);
|
||||
changeTriggered = setTimeout(function() {
|
||||
changeTriggered = null;
|
||||
console.log("dir updated:", dirEntry.dirName);
|
||||
self.dirScanQueue.add(fullDirPath, fullDirPath);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function onWatchError(err) {
|
||||
console.error("watch error:", err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
Player.prototype.getOrCreateDir = function (dirName, stat) {
|
||||
var dirEntry = this.dirs[dirName];
|
||||
|
||||
if (!dirEntry) {
|
||||
dirEntry = this.dirs[dirName] = {
|
||||
dirName: dirName,
|
||||
entries: {},
|
||||
dirEntries: {},
|
||||
watcher: null, // will be set just below
|
||||
mtime: stat && stat.mtime,
|
||||
};
|
||||
} else if (stat && dirEntry.mtime !== stat.mtime) {
|
||||
dirEntry.mtime = stat.mtime;
|
||||
}
|
||||
if (!dirEntry.watcher) this.watchDirEntry(dirEntry);
|
||||
return dirEntry;
|
||||
};
|
||||
|
||||
|
||||
Player.prototype.getCurPos = function() {
|
||||
return this.isPlaying ?
|
||||
((new Date() - this.trackStartDate) / 1000.0) : this.pausedTime;
|
||||
|
|
@ -533,6 +568,17 @@ Player.prototype.importFile = function(fullPath, filenameHint, cb) {
|
|||
});
|
||||
};
|
||||
|
||||
Player.prototype.persistDirEntry = function(dirEntry, cb) {
|
||||
cb = cb || logIfError;
|
||||
this.db.put(LIBRARY_DIR_PREFIX + dirEntry.dirName, serializeDirEntry(dirEntry), cb);
|
||||
|
||||
function logIfError(err) {
|
||||
if (err) {
|
||||
console.error("unable to persist db entry:", dirEntry, err.stack);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Player.prototype.persist = function(dbFile, cb) {
|
||||
cb = cb || logIfError;
|
||||
var prevDbFile = this.libraryIndex.trackTable[dbFile.key];
|
||||
|
|
@ -1168,8 +1214,7 @@ function isFileIgnored(basename) {
|
|||
}
|
||||
|
||||
function deserializeFileData(dataStr) {
|
||||
var obj = JSON.parse(dataStr);
|
||||
return obj;
|
||||
return JSON.parse(dataStr);
|
||||
}
|
||||
|
||||
function trackWithoutIndex(dbFile) {
|
||||
|
|
@ -1184,6 +1229,15 @@ function serializeFileData(dbFile) {
|
|||
return JSON.stringify(trackWithoutIndex(dbFile));
|
||||
}
|
||||
|
||||
function serializeDirEntry(dirEntry) {
|
||||
return JSON.stringify({
|
||||
dirName: dirEntry.dirName,
|
||||
entries: dirEntry.entries,
|
||||
dirEntries: dirEntry.dirEntries,
|
||||
mtime: dirEntry.mtime,
|
||||
});
|
||||
}
|
||||
|
||||
function trackNameFromFile(filename) {
|
||||
var basename = path.basename(filename);
|
||||
var ext = path.extname(basename);
|
||||
|
|
|
|||
Loading…
Reference in a new issue