Merge pull request #5456 from NodeBB/sounds-refactor

Sound system improvements
This commit is contained in:
Julian Lam
2017-02-21 16:14:58 -05:00
committed by GitHub
16 changed files with 338 additions and 260 deletions

View File

@@ -37,11 +37,8 @@ settingsController.get = function (req, res, callback) {
homePageRoutes: function (next) {
getHomePageRoutes(next);
},
sounds: function (next) {
meta.sounds.getFiles(next);
},
soundsMapping: function (next) {
meta.sounds.getMapping(userData.uid, next);
meta.sounds.getUserSoundMap(userData.uid, next);
}
}, next);
},
@@ -50,19 +47,47 @@ settingsController.get = function (req, res, callback) {
userData.languages = results.languages;
userData.homePageRoutes = results.homePageRoutes;
var soundSettings = {
'notificationSound': 'notification',
'incomingChatSound': 'chat-incoming',
'outgoingChatSound': 'chat-outgoing'
var types = [
'notification',
'chat-incoming',
'chat-outgoing',
];
var aliases = {
'notification': 'notificationSound',
'chat-incoming': 'incomingChatSound',
'chat-outgoing': 'outgoingChatSound',
};
Object.keys(soundSettings).forEach(function (setting) {
userData[setting] = Object.keys(results.sounds).map(function (name) {
return {name: name, selected: name === results.soundsMapping[soundSettings[setting]]};
types.forEach(function (type) {
var soundpacks = plugins.soundpacks.map(function (pack) {
var sounds = Object.keys(pack.sounds).map(function (soundName) {
var value = pack.name + ' | ' + soundName;
return {
name: soundName,
value: value,
selected: value === results.soundsMapping[type],
};
});
return {
name: pack.name,
sounds: sounds,
};
});
userData[type + '-sound'] = soundpacks;
// fallback
userData[aliases[type]] = soundpacks.concat.apply([], soundpacks.map(function (pack) {
return pack.sounds.map(function (sound) {
return {
name: sound.value,
selected: sound.selected,
};
});
}));
});
plugins.fireHook('filter:user.customSettings', {settings: results.settings, customSettings: [], uid: req.uid}, next);
plugins.fireHook('filter:user.customSettings', { settings: results.settings, customSettings: [], uid: req.uid }, next);
},
function (data, next) {
userData.customSettings = data.customSettings;
@@ -75,10 +100,10 @@ settingsController.get = function (req, res, callback) {
}
userData.dailyDigestFreqOptions = [
{value: 'off', name: '[[user:digest_off]]', selected: 'off' === userData.settings.dailyDigestFreq},
{value: 'day', name: '[[user:digest_daily]]', selected: 'day' === userData.settings.dailyDigestFreq},
{value: 'week', name: '[[user:digest_weekly]]', selected: 'week' === userData.settings.dailyDigestFreq},
{value: 'month', name: '[[user:digest_monthly]]', selected: 'month' === userData.settings.dailyDigestFreq}
{ value: 'off', name: '[[user:digest_off]]', selected: 'off' === userData.settings.dailyDigestFreq },
{ value: 'day', name: '[[user:digest_daily]]', selected: 'day' === userData.settings.dailyDigestFreq },
{ value: 'week', name: '[[user:digest_weekly]]', selected: 'week' === userData.settings.dailyDigestFreq },
{ value: 'month', name: '[[user:digest_monthly]]', selected: 'month' === userData.settings.dailyDigestFreq }
];

View File

@@ -1,24 +1,46 @@
'use strict';
var meta = require('../../meta');
var plugins = require('../../plugins');
var db = require('../../database');
var soundsController = {};
soundsController.get = function (req, res, next) {
meta.sounds.getFiles(function (err, sounds) {
db.getObject('settings:sounds', function (err, settings) {
if (err) {
return next(err);
}
settings = settings || {};
sounds = Object.keys(sounds).map(function (name) {
return {
name: name
};
var types = [
'notification',
'chat-incoming',
'chat-outgoing',
];
var output = {};
types.forEach(function (type) {
var soundpacks = plugins.soundpacks.map(function (pack) {
var sounds = Object.keys(pack.sounds).map(function (soundName) {
var value = pack.name + ' | ' + soundName;
return {
name: soundName,
value: value,
selected: value === settings[type],
};
});
return {
name: pack.name,
sounds: sounds,
};
});
output[type + '-sound'] = soundpacks;
});
res.render('admin/general/sounds', {
sounds: sounds
});
res.render('admin/general/sounds', output);
});
};

View File

@@ -5,6 +5,8 @@ var path = require('path');
var async = require('async');
var nconf = require('nconf');
var winston = require('winston');
var meta = require('../../meta');
var file = require('../../file');
var image = require('../../image');
var plugins = require('../../plugins');
@@ -105,12 +107,7 @@ uploadsController.uploadSound = function (req, res, next) {
return next(err);
}
var soundsPath = path.join(__dirname, '../../../build/public/sounds'),
filePath = path.join(nconf.get('upload_path'), 'sounds', uploadedFile.name);
file.link(filePath, path.join(soundsPath, path.basename(filePath)));
fs.unlink(uploadedFile.path, function (err) {
meta.sounds.build(function (err) {
if (err) {
return next(err);
}

View File

@@ -5,7 +5,7 @@ var winston = require('winston');
var buildStart;
var valid = ['js', 'clientCSS', 'acpCSS', 'tpl', 'lang'];
var valid = ['js', 'clientCSS', 'acpCSS', 'tpl', 'lang', 'sound'];
exports.buildAll = function (callback) {
exports.build(valid.join(','), callback);
@@ -46,7 +46,7 @@ exports.buildTargets = function (targets, callback) {
var cacheBuster = require('./cacheBuster');
var meta = require('../meta');
var numCpus = require('os').cpus().length;
var strategy = (targets.length > 1 && numCpus > 1);
var parallel = targets.length > 1 && numCpus > 1;
buildStart = buildStart || Date.now();
@@ -59,13 +59,13 @@ exports.buildTargets = function (targets, callback) {
next();
};
if (strategy) {
if (parallel) {
winston.verbose('[build] Utilising multiple cores/processes');
} else {
winston.verbose('[build] Utilising single-core');
}
async[strategy ? 'parallel' : 'series']([
async[parallel ? 'parallel' : 'series']([
function (next) {
if (targets.indexOf('js') !== -1) {
winston.info('[build] Building javascript');
@@ -111,6 +111,12 @@ exports.buildTargets = function (targets, callback) {
meta.languages.build(step.bind(this, startTime, target, next));
break;
case 'sound':
winston.info('[build] Linking sound files');
startTime = Date.now();
meta.sounds.build(step.bind(this, startTime, target, next));
break;
default:
winston.warn('[build] Unknown build target: \'' + target + '\'');
setImmediate(next);

View File

@@ -2,71 +2,101 @@
var path = require('path');
var fs = require('fs');
var nconf = require('nconf');
var winston = require('winston');
var rimraf = require('rimraf');
var mkdirp = require('mkdirp');
var async = require('async');
var file = require('../file');
var plugins = require('../plugins');
var user = require('../user');
var db = require('../database');
module.exports = function (Meta) {
var soundsPath = path.join(__dirname, '../../build/public/sounds');
var uploadsPath = path.join(__dirname, '../../public/uploads/sounds');
module.exports = function (Meta) {
Meta.sounds = {};
Meta.sounds.init = function (callback) {
if (nconf.get('isPrimary') === 'true') {
setupSounds(callback);
} else {
if (typeof callback === 'function') {
callback();
}
}
};
Meta.sounds.addUploads = function addUploads(callback) {
fs.readdir(uploadsPath, function (err, files) {
if (err) {
if (err.code !== 'ENOENT') {
return callback(err);
}
Meta.sounds.getFiles = function (callback) {
async.waterfall([
function (next) {
fs.readdir(path.join(__dirname, '../../build/public/sounds'), next);
},
function (sounds, next) {
fs.readdir(path.join(nconf.get('upload_path'), 'sounds'), function (err, uploaded) {
if (err) {
if (err.code === 'ENOENT') {
return next(null, sounds);
}
return next(err);
}
next(null, sounds.concat(uploaded));
files = [];
}
var uploadSounds = files.reduce(function (prev, fileName) {
var name = fileName.split('.');
if (!name.length || !name[0].length) {
return prev;
}
name = name[0];
name = name[0].toUpperCase() + name.slice(1);
prev[name] = fileName;
return prev;
}, {});
plugins.soundpacks = plugins.soundpacks.filter(function (pack) {
return pack.name !== 'Uploads';
});
if (Object.keys(uploadSounds).length) {
plugins.soundpacks.push({
name: 'Uploads',
id: 'uploads',
dir: uploadsPath,
sounds: uploadSounds,
});
}
], function (err, files) {
if (err) {
winston.error('Could not get local sound files:' + err.message);
console.log(err.stack);
return callback(null, []);
}
var localList = {};
// Filter out hidden files
files = files.filter(function (filename) {
return !filename.startsWith('.');
});
// Return proper paths
files.forEach(function (filename) {
localList[filename] = nconf.get('relative_path') + '/assets/sounds/' + filename;
});
callback(null, localList);
callback();
});
};
Meta.sounds.getMapping = function (uid, callback) {
var user = require('../user');
Meta.sounds.build = function build(callback) {
Meta.sounds.addUploads(function (err) {
if (err) {
return callback(err);
}
var map = plugins.soundpacks.map(function (pack) {
return Object.keys(pack.sounds).reduce(function (prev, soundName) {
var soundPath = pack.sounds[soundName];
prev[pack.name + ' | ' + soundName] = pack.id + '/' + soundPath;
return prev;
}, {});
});
map.unshift({});
map = Object.assign.apply(null, map);
async.series([
function (next) {
rimraf(soundsPath, next);
},
function (next) {
mkdirp(soundsPath, next);
},
function (cb) {
async.parallel([
function (next) {
fs.writeFile(path.join(soundsPath, 'fileMap.json'), JSON.stringify(map), next);
},
function (next) {
async.each(plugins.soundpacks, function (pack, next) {
file.linkDirs(pack.dir, path.join(soundsPath, pack.id), next);
}, next);
},
], cb);
},
], callback);
});
};
var keys = ['chat-incoming', 'chat-outgoing', 'notification'];
Meta.sounds.getUserSoundMap = function getUserSoundMap(uid, callback) {
async.parallel({
defaultMapping: function (next) {
db.getObject('settings:sounds', next);
@@ -78,82 +108,25 @@ module.exports = function (Meta) {
if (err) {
return callback(err);
}
var userSettings = results.userSettings;
userSettings = {
notification: userSettings.notificationSound,
'chat-incoming': userSettings.incomingChatSound,
'chat-outgoing': userSettings.outgoingChatSound,
};
var defaultMapping = results.defaultMapping || {};
var soundMapping = {};
soundMapping.notification = (userSettings.notificationSound || userSettings.notificationSound === '') ?
userSettings.notificationSound : defaultMapping.notification || '';
soundMapping['chat-incoming'] = (userSettings.incomingChatSound || userSettings.incomingChatSound === '') ?
userSettings.incomingChatSound : defaultMapping['chat-incoming'] || '';
soundMapping['chat-outgoing'] = (userSettings.outgoingChatSound || userSettings.outgoingChatSound === '') ?
userSettings.outgoingChatSound : defaultMapping['chat-outgoing'] || '';
keys.forEach(function (key) {
if (userSettings[key] || userSettings[key] === '') {
soundMapping[key] = userSettings[key] || null;
} else {
soundMapping[key] = defaultMapping[key] || null;
}
});
callback(null, soundMapping);
});
};
function setupSounds(callback) {
var soundsPath = path.join(__dirname, '../../build/public/sounds');
async.waterfall([
function (next) {
fs.readdir(path.join(nconf.get('upload_path'), 'sounds'), function (err, files) {
if (err) {
if (err.code === 'ENOENT') {
return next(null, []);
}
return next(err);
}
next(null, files);
});
},
function (uploaded, next) {
uploaded = uploaded.filter(function (filename) {
return !filename.startsWith('.');
}).map(function (filename) {
return path.join(nconf.get('upload_path'), 'sounds', filename);
});
plugins.fireHook('filter:sounds.get', uploaded, function (err, filePaths) {
if (err) {
winston.error('Could not initialise sound files:' + err.message);
return;
}
// Clear the sounds directory
async.series([
function (next) {
rimraf(soundsPath, next);
},
function (next) {
mkdirp(soundsPath, next);
}
], function (err) {
if (err) {
winston.error('Could not initialise sound files:' + err.message);
return;
}
// Link paths
async.each(filePaths, function (filePath, next) {
file.link(filePath, path.join(soundsPath, path.basename(filePath)), next);
}, function (err) {
if (!err) {
winston.verbose('[sounds] Sounds OK');
} else {
winston.error('[sounds] Could not initialise sounds: ' + err.message);
}
if (typeof next === 'function') {
next();
}
});
});
});
}
], callback);
}
};
};

View File

@@ -32,6 +32,7 @@ var middleware;
Plugins.libraryPaths = [];
Plugins.versionWarning = [];
Plugins.languageCodes = [];
Plugins.soundpacks = [];
Plugins.initialized = false;

View File

@@ -41,6 +41,7 @@ module.exports = function (Plugins) {
Plugins.lessFiles.length = 0;
Plugins.clientScripts.length = 0;
Plugins.acpScripts.length = 0;
Plugins.soundpacks.length = 0;
async.waterfall([
async.apply(Plugins.getPluginPaths),
@@ -57,6 +58,7 @@ module.exports = function (Plugins) {
async.apply(mapClientSideScripts, pluginData),
async.apply(mapClientModules, pluginData),
async.apply(mapStaticDirectories, pluginData, pluginData.path),
async.apply(mapSoundpack, pluginData),
], next);
}, next);
}
@@ -93,6 +95,9 @@ module.exports = function (Plugins) {
function (next) {
mapClientModules(pluginData, next);
},
function (next) {
mapSoundpack(pluginData, next);
},
], function (err) {
if (err) {
winston.verbose('[plugins] Could not load plugin : ' + pluginData.id);
@@ -251,6 +256,35 @@ module.exports = function (Plugins) {
callback();
}
function mapSoundpack(pluginData, callback) {
var soundpack = pluginData.soundpack;
if (!soundpack || !soundpack.dir || !soundpack.sounds) {
return callback();
}
soundpack.name = soundpack.name || pluginData.name;
soundpack.id = pluginData.id;
soundpack.dir = path.join(pluginData.path, soundpack.dir);
async.each(Object.keys(soundpack.sounds), function (key, next) {
file.exists(path.join(soundpack.dir, soundpack.sounds[key]), function (exists) {
if (!exists) {
delete soundpack.sounds[key];
}
next();
});
}, function (err) {
if (err) {
return callback(err);
}
if (Object.keys(soundpack.sounds).length) {
Plugins.soundpacks.push(soundpack);
}
callback();
});
}
function resolveModulePath(fullPath, relPath) {
/**
* With npm@3, dependencies can become flattened, and appear at the root level.

View File

@@ -341,20 +341,8 @@ SocketModules.chats.getMessages = function (socket, data, callback) {
};
/* Sounds */
SocketModules.sounds.getSounds = function (socket, data, callback) {
// Read sounds from local directory
meta.sounds.getFiles(callback);
};
SocketModules.sounds.getMapping = function (socket, data, callback) {
meta.sounds.getMapping(socket.uid, callback);
};
SocketModules.sounds.getData = function (socket, data, callback) {
async.parallel({
mapping: async.apply(meta.sounds.getMapping, socket.uid),
files: async.apply(meta.sounds.getFiles)
}, callback);
SocketModules.sounds.getUserSoundMap = function getUserSoundMap(socket, data, callback) {
meta.sounds.getUserSoundMap(socket.uid, callback);
};
module.exports = SocketModules;

View File

@@ -9,9 +9,15 @@
<div class="form-group col-xs-9">
<select class="form-control" id="notification" name="notification">
<option value="">[[user:no-sound]]</option>
<!-- BEGIN sounds -->
<option value="{sounds.name}">{sounds.name}</option>
<!-- END sounds -->
<!-- BEGIN notification-sound -->
<optgroup label="{notification-sound.name}">
<!-- BEGIN notification-sound.sounds -->
<option value="{notification-sound.sounds.value}" <!-- IF notification-sound.sounds.selected -->selected<!-- ENDIF notification-sound.sounds.selected -->>
{notification-sound.sounds.name}
</option>
<!-- END notification-sound.sounds -->
</optgroup>
<!-- END notification-sound -->
</select>
</div>
<div class="btn-group col-xs-3">
@@ -29,9 +35,15 @@
<div class="form-group col-xs-9">
<select class="form-control" id="chat-incoming" name="chat-incoming">
<option value="">[[user:no-sound]]</option>
<!-- BEGIN sounds -->
<option value="{sounds.name}">{sounds.name}</option>
<!-- END sounds -->
<!-- BEGIN chat-incoming-sound -->
<optgroup label="{chat-incoming-sound.name}">
<!-- BEGIN chat-incoming-sound.sounds -->
<option value="{chat-incoming-sound.sounds.value}" <!-- IF chat-incoming-sound.sounds.selected -->selected<!-- ENDIF chat-incoming-sound.sounds.selected -->>
{chat-incoming-sound.sounds.name}
</option>
<!-- END chat-incoming-sound.sounds -->
</optgroup>
<!-- END chat-incoming-sound -->
</select>
</div>
<div class="btn-group col-xs-3">
@@ -44,9 +56,15 @@
<div class="form-group col-xs-9">
<select class="form-control" id="chat-outgoing" name="chat-outgoing">
<option value="">[[user:no-sound]]</option>
<!-- BEGIN sounds -->
<option value="{sounds.name}">{sounds.name}</option>
<!-- END sounds -->
<!-- BEGIN chat-outgoing-sound -->
<optgroup label="{chat-outgoing-sound.name}">
<!-- BEGIN chat-outgoing-sound.sounds -->
<option value="{chat-outgoing-sound.sounds.value}" <!-- IF chat-outgoing-sound.sounds.selected -->selected<!-- ENDIF chat-outgoing-sound.sounds.selected -->>
{chat-outgoing-sound.sounds.name}
</option>
<!-- END chat-outgoing-sound.sounds -->
</optgroup>
<!-- END chat-outgoing-sound -->
</select>
</div>
<div class="btn-group col-xs-3">
@@ -56,7 +74,15 @@
<div class="input-group">
<span class="input-group-btn">
<input data-action="upload" data-title="Upload Sound" data-route="{config.relative_path}/api/admin/upload/sound" type="button" class="btn btn-primary" value="[[admin/general/sounds:upload-new-sound]]"></input>
<input
data-action="upload"
data-title="Upload Sound"
data-route="{config.relative_path}/api/admin/upload/sound"
data-accept="audio/*"
type="button"
class="btn btn-primary"
value="[[admin/general/sounds:upload-new-sound]]"
></input>
</span>
</div>
</div>

View File

@@ -103,9 +103,9 @@ function initializeNodeBB(callback) {
},
function (next) {
async.series([
async.apply(meta.sounds.init),
async.apply(languages.init),
async.apply(meta.blacklist.load)
meta.sounds.addUploads,
languages.init,
meta.blacklist.load,
], next);
}
], callback);