detect file deletions on startup. closes #116

This commit is contained in:
Andrew Kelley 2014-02-28 10:47:22 -05:00
parent fbc6386960
commit 0ca3c286f2

View file

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