mirror of https://github.com/iconify/api.git
Prettier all files, update dependencies
This commit is contained in:
parent
6adfc9d131
commit
d28f8f095f
|
|
@ -0,0 +1,13 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{*.json,*.yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"trailingComma": "es5",
|
||||
"singleQuote": true,
|
||||
"useTabs": true,
|
||||
"semi": true,
|
||||
"quoteProps": "consistent"
|
||||
}
|
||||
264
app.js
264
app.js
|
|
@ -7,52 +7,54 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
// Load required modules
|
||||
const fs = require('fs'),
|
||||
util = require('util'),
|
||||
express = require('express');
|
||||
util = require('util'),
|
||||
express = require('express');
|
||||
|
||||
// Log uncaught exceptions to stderr
|
||||
process.on('uncaughtException', function (err) {
|
||||
console.error('Uncaught exception:', err);
|
||||
process.on('uncaughtException', function(err) {
|
||||
console.error('Uncaught exception:', err);
|
||||
});
|
||||
|
||||
// Create application
|
||||
let app = {
|
||||
root: __dirname
|
||||
root: __dirname,
|
||||
};
|
||||
|
||||
/**
|
||||
* Load config.json and config-default.json
|
||||
*/
|
||||
app.config = JSON.parse(fs.readFileSync(__dirname + '/config-default.json', 'utf8'));
|
||||
app.config = JSON.parse(
|
||||
fs.readFileSync(__dirname + '/config-default.json', 'utf8')
|
||||
);
|
||||
|
||||
try {
|
||||
let customConfig = fs.readFileSync(__dirname + '/config.json', 'utf8');
|
||||
if (typeof customConfig === 'string') {
|
||||
try {
|
||||
customConfig = JSON.parse(customConfig);
|
||||
Object.keys(customConfig).forEach(key => {
|
||||
if (typeof app.config[key] !== typeof customConfig[key]) {
|
||||
return;
|
||||
}
|
||||
let customConfig = fs.readFileSync(__dirname + '/config.json', 'utf8');
|
||||
if (typeof customConfig === 'string') {
|
||||
try {
|
||||
customConfig = JSON.parse(customConfig);
|
||||
Object.keys(customConfig).forEach(key => {
|
||||
if (typeof app.config[key] !== typeof customConfig[key]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof app.config[key] === 'object') {
|
||||
// merge object
|
||||
Object.assign(app.config[key], customConfig[key]);
|
||||
} else {
|
||||
// overwrite scalar variables
|
||||
app.config[key] = customConfig[key];
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error parsing config.json', err);
|
||||
}
|
||||
}
|
||||
if (typeof app.config[key] === 'object') {
|
||||
// merge object
|
||||
Object.assign(app.config[key], customConfig[key]);
|
||||
} else {
|
||||
// overwrite scalar variables
|
||||
app.config[key] = customConfig[key];
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error parsing config.json', err);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Missing config.json. Using default API configuration');
|
||||
console.log('Missing config.json. Using default API configuration');
|
||||
}
|
||||
|
||||
// Add logging and mail modules
|
||||
|
|
@ -69,22 +71,27 @@ app.logger = require('./src/logger').bind(this, app);
|
|||
*/
|
||||
// Port
|
||||
if (app.config['env-port'] && process.env.PORT) {
|
||||
app.config.port = process.env.PORT;
|
||||
app.config.port = process.env.PORT;
|
||||
}
|
||||
|
||||
// Region file to easy identify server in CDN
|
||||
if (!app.config['env-region'] && process.env.region) {
|
||||
app.config.region = process.env.region;
|
||||
app.config.region = process.env.region;
|
||||
}
|
||||
if (app.config.region.length > 10 || !app.config.region.match(/^[a-z0-9_-]+$/i)) {
|
||||
app.config.region = '';
|
||||
app.error('Invalid value for region config variable.');
|
||||
if (
|
||||
app.config.region.length > 10 ||
|
||||
!app.config.region.match(/^[a-z0-9_-]+$/i)
|
||||
) {
|
||||
app.config.region = '';
|
||||
app.error('Invalid value for region config variable.');
|
||||
}
|
||||
|
||||
// Reload secret key
|
||||
if (app.config['reload-secret'] === '') {
|
||||
// Add reload-secret to config.json to be able to run /reload?key=your-secret-key that will reload collections without restarting server
|
||||
console.log('reload-secret configuration is empty. You will not be able to update all collections without restarting server.');
|
||||
// Add reload-secret to config.json to be able to run /reload?key=your-secret-key that will reload collections without restarting server
|
||||
console.log(
|
||||
'reload-secret configuration is empty. You will not be able to update all collections without restarting server.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -92,7 +99,9 @@ if (app.config['reload-secret'] === '') {
|
|||
*/
|
||||
|
||||
// Get version
|
||||
app.version = JSON.parse(fs.readFileSync(__dirname + '/package.json', 'utf8')).version;
|
||||
app.version = JSON.parse(
|
||||
fs.readFileSync(__dirname + '/package.json', 'utf8')
|
||||
).version;
|
||||
|
||||
// Files helper
|
||||
app.fs = require('./src/files')(app);
|
||||
|
|
@ -103,8 +112,10 @@ app.loadJSON = require('./src/json').bind(this, app);
|
|||
// Add directories storage
|
||||
app.dirs = require('./src/dirs')(app);
|
||||
if (!app.dirs.getRepos().length) {
|
||||
console.error('No repositories found. Make sure either Iconify or custom repository is set in configuration.');
|
||||
return;
|
||||
console.error(
|
||||
'No repositories found. Make sure either Iconify or custom repository is set in configuration.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Collections
|
||||
|
|
@ -120,88 +131,123 @@ app.iconsRequest = require('./src/request-icons').bind(this, app);
|
|||
app.miscRequest = require('./src/request').bind(this, app);
|
||||
|
||||
// Start application
|
||||
require('./src/startup')(app).then(() => {
|
||||
require('./src/startup')(app)
|
||||
.then(() => {
|
||||
// Create HTTP server
|
||||
app.server = express();
|
||||
|
||||
// Create HTTP server
|
||||
app.server = express();
|
||||
// Disable X-Powered-By header
|
||||
app.server.disable('x-powered-by');
|
||||
|
||||
// Disable X-Powered-By header
|
||||
app.server.disable('x-powered-by');
|
||||
// CORS
|
||||
app.server.options('/*', (req, res) => {
|
||||
if (app.config.cors) {
|
||||
res.header(
|
||||
'Access-Control-Allow-Origin',
|
||||
app.config.cors.origins
|
||||
);
|
||||
res.header(
|
||||
'Access-Control-Allow-Methods',
|
||||
app.config.cors.methods
|
||||
);
|
||||
res.header(
|
||||
'Access-Control-Allow-Headers',
|
||||
app.config.cors.headers
|
||||
);
|
||||
res.header('Access-Control-Max-Age', app.config.cors.timeout);
|
||||
}
|
||||
res.send(200);
|
||||
});
|
||||
|
||||
// CORS
|
||||
app.server.options('/*', (req, res) => {
|
||||
if (app.config.cors) {
|
||||
res.header('Access-Control-Allow-Origin', app.config.cors.origins);
|
||||
res.header('Access-Control-Allow-Methods', app.config.cors.methods);
|
||||
res.header('Access-Control-Allow-Headers', app.config.cors.headers);
|
||||
res.header('Access-Control-Max-Age', app.config.cors.timeout);
|
||||
}
|
||||
res.send(200);
|
||||
});
|
||||
// GET 3 part request
|
||||
app.server.get(
|
||||
/^\/([a-z0-9-]+)\/([a-z0-9-]+)\.(js|json|svg)$/,
|
||||
(req, res) => {
|
||||
// prefix/icon.svg
|
||||
// prefix/icons.json
|
||||
app.iconsRequest(
|
||||
req,
|
||||
res,
|
||||
req.params[0],
|
||||
req.params[1],
|
||||
req.params[2]
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// GET 3 part request
|
||||
app.server.get(/^\/([a-z0-9-]+)\/([a-z0-9-]+)\.(js|json|svg)$/, (req, res) => {
|
||||
// prefix/icon.svg
|
||||
// prefix/icons.json
|
||||
app.iconsRequest(req, res, req.params[0], req.params[1], req.params[2]);
|
||||
});
|
||||
// GET 2 part JS/JSON request
|
||||
app.server.get(/^\/([a-z0-9-]+)\.(js|json)$/, (req, res) => {
|
||||
// prefix.json
|
||||
app.iconsRequest(req, res, req.params[0], 'icons', req.params[1]);
|
||||
});
|
||||
|
||||
// GET 2 part JS/JSON request
|
||||
app.server.get(/^\/([a-z0-9-]+)\.(js|json)$/, (req, res) => {
|
||||
// prefix.json
|
||||
app.iconsRequest(req, res, req.params[0], 'icons', req.params[1]);
|
||||
});
|
||||
// GET 2 part SVG request
|
||||
app.server.get(/^\/([a-z0-9:-]+)\.svg$/, (req, res) => {
|
||||
let parts = req.params[0].split(':');
|
||||
|
||||
// GET 2 part SVG request
|
||||
app.server.get(/^\/([a-z0-9:-]+)\.svg$/, (req, res) => {
|
||||
let parts = req.params[0].split(':');
|
||||
if (parts.length === 2) {
|
||||
// prefix:icon.svg
|
||||
app.iconsRequest(req, res, parts[0], parts[1], 'svg');
|
||||
return;
|
||||
}
|
||||
|
||||
if (parts.length === 2) {
|
||||
// prefix:icon.svg
|
||||
app.iconsRequest(req, res, parts[0], parts[1], 'svg');
|
||||
return;
|
||||
}
|
||||
if (parts.length === 1) {
|
||||
parts = parts[0].split('-');
|
||||
if (parts.length > 1) {
|
||||
// prefix-icon.svg
|
||||
app.iconsRequest(
|
||||
req,
|
||||
res,
|
||||
parts.shift(),
|
||||
parts.join('-'),
|
||||
'svg'
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (parts.length === 1) {
|
||||
parts = parts[0].split('-');
|
||||
if (parts.length > 1) {
|
||||
// prefix-icon.svg
|
||||
app.iconsRequest(req, res, parts.shift(), parts.join('-'), 'svg');
|
||||
return;
|
||||
}
|
||||
}
|
||||
app.response(req, res, 404);
|
||||
});
|
||||
|
||||
app.response(req, res, 404);
|
||||
});
|
||||
// Send robots.txt that disallows everything
|
||||
app.server.get('/robots.txt', (req, res) =>
|
||||
app.miscRequest(req, res, 'robots')
|
||||
);
|
||||
app.server.post('/robots.txt', (req, res) =>
|
||||
app.miscRequest(req, res, 'robots')
|
||||
);
|
||||
|
||||
// Send robots.txt that disallows everything
|
||||
app.server.get('/robots.txt', (req, res) => app.miscRequest(req, res, 'robots'));
|
||||
app.server.post('/robots.txt', (req, res) => app.miscRequest(req, res, 'robots'));
|
||||
// API version information
|
||||
app.server.get('/version', (req, res) =>
|
||||
app.miscRequest(req, res, 'version')
|
||||
);
|
||||
|
||||
// API version information
|
||||
app.server.get('/version', (req, res) => app.miscRequest(req, res, 'version'));
|
||||
|
||||
// Reload collections without restarting app
|
||||
app.server.get('/reload', (req, res) => app.miscRequest(req, res, 'reload'));
|
||||
app.server.post('/reload', (req, res) => app.miscRequest(req, res, 'reload'));
|
||||
|
||||
// Get latest collection from Git repository
|
||||
app.server.get('/sync', (req, res) => app.miscRequest(req, res, 'sync'));
|
||||
app.server.post('/sync', (req, res) => app.miscRequest(req, res, 'sync'));
|
||||
|
||||
// Redirect home page
|
||||
app.server.get('/', (req, res) => {
|
||||
res.redirect(301, app.config['index-page']);
|
||||
});
|
||||
|
||||
// Create server
|
||||
app.server.listen(app.config.port, () => {
|
||||
app.log('Listening on port ' + app.config.port);
|
||||
});
|
||||
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
// Reload collections without restarting app
|
||||
app.server.get('/reload', (req, res) =>
|
||||
app.miscRequest(req, res, 'reload')
|
||||
);
|
||||
app.server.post('/reload', (req, res) =>
|
||||
app.miscRequest(req, res, 'reload')
|
||||
);
|
||||
|
||||
// Get latest collection from Git repository
|
||||
app.server.get('/sync', (req, res) =>
|
||||
app.miscRequest(req, res, 'sync')
|
||||
);
|
||||
app.server.post('/sync', (req, res) =>
|
||||
app.miscRequest(req, res, 'sync')
|
||||
);
|
||||
|
||||
// Redirect home page
|
||||
app.server.get('/', (req, res) => {
|
||||
res.redirect(301, app.config['index-page']);
|
||||
});
|
||||
|
||||
// Create server
|
||||
app.server.listen(app.config.port, () => {
|
||||
app.log('Listening on port ' + app.config.port);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,52 +1,52 @@
|
|||
{
|
||||
"port": 3000,
|
||||
"env-port": true,
|
||||
"region": "",
|
||||
"env-region": true,
|
||||
"reload-secret": "",
|
||||
"custom-icons-dir": "{dir}/json",
|
||||
"serve-default-icons": true,
|
||||
"index-page": "https://iconify.design/",
|
||||
"json-loader": "parse",
|
||||
"cache": {
|
||||
"timeout": 604800,
|
||||
"min-refresh": 604800,
|
||||
"private": false
|
||||
},
|
||||
"cors": {
|
||||
"origins": "*",
|
||||
"timeout": 86400,
|
||||
"methods": "GET, OPTIONS",
|
||||
"headers": "Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding"
|
||||
},
|
||||
"sync": {
|
||||
"sync-on-startup": "missing",
|
||||
"sync-delay": 60,
|
||||
"repeated-sync-delay": 60,
|
||||
"versions": "{dir}/git-repos/versions.json",
|
||||
"storage": "{dir}/git-repos",
|
||||
"git": "git clone {repo} --depth 1 --no-tags {target}",
|
||||
"secret": "",
|
||||
"iconify": "https://github.com/iconify/collections-json.git",
|
||||
"custom": "",
|
||||
"custom-dir": "",
|
||||
"rm": "rm -rf {dir}"
|
||||
},
|
||||
"mail": {
|
||||
"active": false,
|
||||
"throttle": 30,
|
||||
"repeat": 180,
|
||||
"from": "noreply@localhost",
|
||||
"to": "noreply@localhost",
|
||||
"subject": "Iconify API log",
|
||||
"transport": {
|
||||
"host": "smtp.ethereal.email",
|
||||
"port": 587,
|
||||
"secure": false,
|
||||
"auth": {
|
||||
"user": "username",
|
||||
"pass": "password"
|
||||
}
|
||||
}
|
||||
}
|
||||
"port": 3000,
|
||||
"env-port": true,
|
||||
"region": "",
|
||||
"env-region": true,
|
||||
"reload-secret": "",
|
||||
"custom-icons-dir": "{dir}/json",
|
||||
"serve-default-icons": true,
|
||||
"index-page": "https://iconify.design/",
|
||||
"json-loader": "parse",
|
||||
"cache": {
|
||||
"timeout": 604800,
|
||||
"min-refresh": 604800,
|
||||
"private": false
|
||||
},
|
||||
"cors": {
|
||||
"origins": "*",
|
||||
"timeout": 86400,
|
||||
"methods": "GET, OPTIONS",
|
||||
"headers": "Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding"
|
||||
},
|
||||
"sync": {
|
||||
"sync-on-startup": "missing",
|
||||
"sync-delay": 60,
|
||||
"repeated-sync-delay": 60,
|
||||
"versions": "{dir}/git-repos/versions.json",
|
||||
"storage": "{dir}/git-repos",
|
||||
"git": "git clone {repo} --depth 1 --no-tags {target}",
|
||||
"secret": "",
|
||||
"iconify": "https://github.com/iconify/collections-json.git",
|
||||
"custom": "",
|
||||
"custom-dir": "",
|
||||
"rm": "rm -rf {dir}"
|
||||
},
|
||||
"mail": {
|
||||
"active": false,
|
||||
"throttle": 30,
|
||||
"repeat": 180,
|
||||
"from": "noreply@localhost",
|
||||
"to": "noreply@localhost",
|
||||
"subject": "Iconify API log",
|
||||
"transport": {
|
||||
"host": "smtp.ethereal.email",
|
||||
"port": 587,
|
||||
"secure": false,
|
||||
"auth": {
|
||||
"user": "username",
|
||||
"pass": "password"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
56
package.json
56
package.json
|
|
@ -1,30 +1,30 @@
|
|||
{
|
||||
"version": "2.0.1",
|
||||
"description": "Node.js version of api.iconify.design",
|
||||
"private": true,
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"test": "mocha tests/*_test.js"
|
||||
},
|
||||
"author": "Vjacheslav Trushkin",
|
||||
"license": "MIT",
|
||||
"bugs": "https://github.com/iconify/api.js/issues",
|
||||
"homepage": "https://github.com/iconify/api.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+ssh://git@github.com/iconify/api.js.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/json-tools": "^1.0.6",
|
||||
"express": "^4.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.2.0",
|
||||
"mocha": "^5.2.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@iconify/json": "*",
|
||||
"nodemailer": "^4.6.8"
|
||||
}
|
||||
"version": "2.0.1",
|
||||
"description": "Node.js version of api.iconify.design",
|
||||
"private": true,
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"test": "mocha tests/*_test.js"
|
||||
},
|
||||
"author": "Vjacheslav Trushkin",
|
||||
"license": "MIT",
|
||||
"bugs": "https://github.com/iconify/api.js/issues",
|
||||
"homepage": "https://github.com/iconify/api.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/iconify/api.js.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/json-tools": "^1.0.6",
|
||||
"express": "^4.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.2.0",
|
||||
"mocha": "^5.2.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@iconify/json": "*",
|
||||
"nodemailer": "^4.6.8"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
308
src/dirs.js
308
src/dirs.js
|
|
@ -7,7 +7,7 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
|
|
@ -19,172 +19,180 @@ const fs = require('fs');
|
|||
* @returns {object}
|
||||
*/
|
||||
module.exports = app => {
|
||||
let functions = Object.create(null),
|
||||
dirs = Object.create(null),
|
||||
custom = Object.create(null),
|
||||
repos = [],
|
||||
storageDir = null,
|
||||
versionsFile = null;
|
||||
let functions = Object.create(null),
|
||||
dirs = Object.create(null),
|
||||
custom = Object.create(null),
|
||||
repos = [],
|
||||
storageDir = null,
|
||||
versionsFile = null;
|
||||
|
||||
/**
|
||||
* Get root directory of repository
|
||||
*
|
||||
* @param {string} repo
|
||||
* @returns {string}
|
||||
*/
|
||||
functions.rootDir = repo => dirs[repo] === void 0 ? '' : dirs[repo];
|
||||
/**
|
||||
* Get root directory of repository
|
||||
*
|
||||
* @param {string} repo
|
||||
* @returns {string}
|
||||
*/
|
||||
functions.rootDir = repo => (dirs[repo] === void 0 ? '' : dirs[repo]);
|
||||
|
||||
/**
|
||||
* Get storage directory
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
functions.storageDir = () => storageDir;
|
||||
/**
|
||||
* Get storage directory
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
functions.storageDir = () => storageDir;
|
||||
|
||||
/**
|
||||
* Get icons directory
|
||||
*
|
||||
* @param {string} repo
|
||||
* @returns {string}
|
||||
*/
|
||||
functions.iconsDir = repo => {
|
||||
let dir;
|
||||
/**
|
||||
* Get icons directory
|
||||
*
|
||||
* @param {string} repo
|
||||
* @returns {string}
|
||||
*/
|
||||
functions.iconsDir = repo => {
|
||||
let dir;
|
||||
|
||||
switch (repo) {
|
||||
case 'iconify':
|
||||
dir = functions.rootDir(repo);
|
||||
return dir === '' ? '' : dir + '/json';
|
||||
switch (repo) {
|
||||
case 'iconify':
|
||||
dir = functions.rootDir(repo);
|
||||
return dir === '' ? '' : dir + '/json';
|
||||
|
||||
default:
|
||||
return functions.rootDir(repo);
|
||||
}
|
||||
};
|
||||
default:
|
||||
return functions.rootDir(repo);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set root directory for repository
|
||||
*
|
||||
* @param {string} repo
|
||||
* @param {string} dir
|
||||
*/
|
||||
functions.setRootDir = (repo, dir) => {
|
||||
// Append additional directory from config
|
||||
let extra;
|
||||
try {
|
||||
extra = app.config.sync[repo + '-dir'];
|
||||
} catch (err) {
|
||||
extra = '';
|
||||
}
|
||||
/**
|
||||
* Set root directory for repository
|
||||
*
|
||||
* @param {string} repo
|
||||
* @param {string} dir
|
||||
*/
|
||||
functions.setRootDir = (repo, dir) => {
|
||||
// Append additional directory from config
|
||||
let extra;
|
||||
try {
|
||||
extra = app.config.sync[repo + '-dir'];
|
||||
} catch (err) {
|
||||
extra = '';
|
||||
}
|
||||
|
||||
if (extra !== void 0 && extra !== '') {
|
||||
if (extra.slice(0, 1) !== '/') {
|
||||
extra = '/' + extra;
|
||||
}
|
||||
if (extra.slice(-1) === '/') {
|
||||
extra = extra.slice(0, extra.length - 1);
|
||||
}
|
||||
dir += extra;
|
||||
}
|
||||
if (extra !== void 0 && extra !== '') {
|
||||
if (extra.slice(0, 1) !== '/') {
|
||||
extra = '/' + extra;
|
||||
}
|
||||
if (extra.slice(-1) === '/') {
|
||||
extra = extra.slice(0, extra.length - 1);
|
||||
}
|
||||
dir += extra;
|
||||
}
|
||||
|
||||
// Set directory
|
||||
dirs[repo] = dir;
|
||||
};
|
||||
// Set directory
|
||||
dirs[repo] = dir;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set root directory for repository using repository time
|
||||
*
|
||||
* @param {string} repo
|
||||
* @param {number} time
|
||||
* @param {boolean} [save] True if new versions.json should be saved
|
||||
*/
|
||||
functions.setSynchronizedRepoDir = (repo, time, save) => {
|
||||
let dir = storageDir + '/' + repo + '.' + time;
|
||||
custom[repo] = time;
|
||||
functions.setRootDir(repo, dir);
|
||||
if (save === true) {
|
||||
fs.writeFileSync(versionsFile, JSON.stringify(custom, null, 4), 'utf8');
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Set root directory for repository using repository time
|
||||
*
|
||||
* @param {string} repo
|
||||
* @param {number} time
|
||||
* @param {boolean} [save] True if new versions.json should be saved
|
||||
*/
|
||||
functions.setSynchronizedRepoDir = (repo, time, save) => {
|
||||
let dir = storageDir + '/' + repo + '.' + time;
|
||||
custom[repo] = time;
|
||||
functions.setRootDir(repo, dir);
|
||||
if (save === true) {
|
||||
fs.writeFileSync(
|
||||
versionsFile,
|
||||
JSON.stringify(custom, null, 4),
|
||||
'utf8'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all repositories
|
||||
*
|
||||
* @returns {string[]}
|
||||
*/
|
||||
functions.keys = () => Object.keys(dirs);
|
||||
/**
|
||||
* Get all repositories
|
||||
*
|
||||
* @returns {string[]}
|
||||
*/
|
||||
functions.keys = () => Object.keys(dirs);
|
||||
|
||||
/**
|
||||
* Get all repositories
|
||||
*
|
||||
* @returns {string[]}
|
||||
*/
|
||||
functions.getRepos = () => repos;
|
||||
/**
|
||||
* Get all repositories
|
||||
*
|
||||
* @returns {string[]}
|
||||
*/
|
||||
functions.getRepos = () => repos;
|
||||
|
||||
/**
|
||||
* Check if repository has been synchronized
|
||||
*
|
||||
* @param {string} repo
|
||||
* @return {boolean}
|
||||
*/
|
||||
functions.synchronized = repo => custom[repo] === true;
|
||||
/**
|
||||
* Check if repository has been synchronized
|
||||
*
|
||||
* @param {string} repo
|
||||
* @return {boolean}
|
||||
*/
|
||||
functions.synchronized = repo => custom[repo] === true;
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*/
|
||||
/**
|
||||
* Initialize
|
||||
*/
|
||||
|
||||
// Get synchronized repositories
|
||||
let cached = Object.create(null);
|
||||
app.config.canSync = false;
|
||||
try {
|
||||
if (app.config.sync.versions && app.config.sync.storage) {
|
||||
// Set storage directory and versions.json location
|
||||
storageDir = app.config.sync.storage.replace('{dir}', app.root);
|
||||
versionsFile = app.config.sync.versions.replace('{dir}', app.root);
|
||||
app.config.canSync = true;
|
||||
// Get synchronized repositories
|
||||
let cached = Object.create(null);
|
||||
app.config.canSync = false;
|
||||
try {
|
||||
if (app.config.sync.versions && app.config.sync.storage) {
|
||||
// Set storage directory and versions.json location
|
||||
storageDir = app.config.sync.storage.replace('{dir}', app.root);
|
||||
versionsFile = app.config.sync.versions.replace('{dir}', app.root);
|
||||
app.config.canSync = true;
|
||||
|
||||
// Try getting latest repositories
|
||||
cached = fs.readFileSync(versionsFile, 'utf8');
|
||||
cached = JSON.parse(cached);
|
||||
}
|
||||
} catch (err) {
|
||||
if (typeof cached !== 'object') {
|
||||
cached = Object.create(null);
|
||||
}
|
||||
}
|
||||
// Try getting latest repositories
|
||||
cached = fs.readFileSync(versionsFile, 'utf8');
|
||||
cached = JSON.parse(cached);
|
||||
}
|
||||
} catch (err) {
|
||||
if (typeof cached !== 'object') {
|
||||
cached = Object.create(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (storageDir !== null) {
|
||||
try {
|
||||
fs.mkdirSync(storageDir);
|
||||
} catch (err) {
|
||||
}
|
||||
}
|
||||
if (storageDir !== null) {
|
||||
try {
|
||||
fs.mkdirSync(storageDir);
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
// Set default directories
|
||||
if (app.config['serve-default-icons']) {
|
||||
let key = 'iconify';
|
||||
if (cached && cached[key]) {
|
||||
repos.push(key);
|
||||
functions.setSynchronizedRepoDir(key, cached[key], false);
|
||||
} else {
|
||||
let icons;
|
||||
try {
|
||||
icons = require('@iconify/json');
|
||||
repos.push(key);
|
||||
dirs[key] = icons.rootDir();
|
||||
} catch (err) {
|
||||
app.error('Cannot load Iconify icons because @iconify/json package is not installed');
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set default directories
|
||||
if (app.config['serve-default-icons']) {
|
||||
let key = 'iconify';
|
||||
if (cached && cached[key]) {
|
||||
repos.push(key);
|
||||
functions.setSynchronizedRepoDir(key, cached[key], false);
|
||||
} else {
|
||||
let icons;
|
||||
try {
|
||||
icons = require('@iconify/json');
|
||||
repos.push(key);
|
||||
dirs[key] = icons.rootDir();
|
||||
} catch (err) {
|
||||
app.error(
|
||||
'Cannot load Iconify icons because @iconify/json package is not installed'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (app.config['custom-icons-dir']) {
|
||||
let key = 'custom';
|
||||
repos.push(key);
|
||||
if (cached[key]) {
|
||||
functions.setSynchronizedRepoDir(key, cached[key], false);
|
||||
} else {
|
||||
dirs[key] = app.config['custom-icons-dir'].replace('{dir}', app.root);
|
||||
}
|
||||
}
|
||||
if (app.config['custom-icons-dir']) {
|
||||
let key = 'custom';
|
||||
repos.push(key);
|
||||
if (cached[key]) {
|
||||
functions.setSynchronizedRepoDir(key, cached[key], false);
|
||||
} else {
|
||||
dirs[key] = app.config['custom-icons-dir'].replace(
|
||||
'{dir}',
|
||||
app.root
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return functions;
|
||||
return functions;
|
||||
};
|
||||
|
|
|
|||
174
src/files.js
174
src/files.js
|
|
@ -7,7 +7,7 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const util = require('util');
|
||||
|
|
@ -16,87 +16,115 @@ const promiseEach = require('./promise');
|
|||
let _app;
|
||||
|
||||
let functions = {
|
||||
/**
|
||||
* Remove file
|
||||
*
|
||||
* @param file
|
||||
* @param options
|
||||
* @return {Promise<any>}
|
||||
*/
|
||||
unlink: (file, options) => new Promise((fulfill, reject) => {
|
||||
fs.unlink(file, err => {
|
||||
if (err) {
|
||||
_app.error('Error deleting file ' + file, Object.assign({
|
||||
key: 'unlink-' + file
|
||||
}, typeof options === 'object' ? options : Object.create(null)));
|
||||
}
|
||||
fulfill();
|
||||
})
|
||||
}),
|
||||
/**
|
||||
* Remove file
|
||||
*
|
||||
* @param file
|
||||
* @param options
|
||||
* @return {Promise<any>}
|
||||
*/
|
||||
unlink: (file, options) =>
|
||||
new Promise((fulfill, reject) => {
|
||||
fs.unlink(file, err => {
|
||||
if (err) {
|
||||
_app.error(
|
||||
'Error deleting file ' + file,
|
||||
Object.assign(
|
||||
{
|
||||
key: 'unlink-' + file,
|
||||
},
|
||||
typeof options === 'object'
|
||||
? options
|
||||
: Object.create(null)
|
||||
)
|
||||
);
|
||||
}
|
||||
fulfill();
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* Recursively remove directory
|
||||
*
|
||||
* @param dir
|
||||
* @param options
|
||||
* @return {Promise<any>}
|
||||
*/
|
||||
rmdir: (dir, options) => new Promise((fulfill, reject) => {
|
||||
options = typeof options === 'object' ? options : Object.create(null);
|
||||
/**
|
||||
* Recursively remove directory
|
||||
*
|
||||
* @param dir
|
||||
* @param options
|
||||
* @return {Promise<any>}
|
||||
*/
|
||||
rmdir: (dir, options) =>
|
||||
new Promise((fulfill, reject) => {
|
||||
options =
|
||||
typeof options === 'object' ? options : Object.create(null);
|
||||
|
||||
function done() {
|
||||
fs.rmdir(dir, err => {
|
||||
if (err) {
|
||||
_app.error('Error deleting directory ' + dir, Object.assign({
|
||||
key: 'rmdir-' + dir
|
||||
}, options));
|
||||
}
|
||||
fulfill();
|
||||
});
|
||||
}
|
||||
function done() {
|
||||
fs.rmdir(dir, err => {
|
||||
if (err) {
|
||||
_app.error(
|
||||
'Error deleting directory ' + dir,
|
||||
Object.assign(
|
||||
{
|
||||
key: 'rmdir-' + dir,
|
||||
},
|
||||
options
|
||||
)
|
||||
);
|
||||
}
|
||||
fulfill();
|
||||
});
|
||||
}
|
||||
|
||||
fs.readdir(dir, (err, files) => {
|
||||
if (err) {
|
||||
// fulfill instead of rejecting
|
||||
fulfill();
|
||||
return;
|
||||
}
|
||||
fs.readdir(dir, (err, files) => {
|
||||
if (err) {
|
||||
// fulfill instead of rejecting
|
||||
fulfill();
|
||||
return;
|
||||
}
|
||||
|
||||
let children = Object.create(null);
|
||||
let children = Object.create(null);
|
||||
|
||||
files.forEach(file => {
|
||||
let filename = dir + '/' + file,
|
||||
stats = fs.lstatSync(filename);
|
||||
files.forEach(file => {
|
||||
let filename = dir + '/' + file,
|
||||
stats = fs.lstatSync(filename);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
children[filename] = true;
|
||||
return;
|
||||
}
|
||||
if (stats.isDirectory()) {
|
||||
children[filename] = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (stats.isFile() || stats.isSymbolicLink()) {
|
||||
children[filename] = false;
|
||||
}
|
||||
});
|
||||
if (stats.isFile() || stats.isSymbolicLink()) {
|
||||
children[filename] = false;
|
||||
}
|
||||
});
|
||||
|
||||
promiseEach(Object.keys(children), file => {
|
||||
if (children[file]) {
|
||||
return functions.rmdir(file, options);
|
||||
} else {
|
||||
return functions.unlink(file, options);
|
||||
}
|
||||
}).then(() => {
|
||||
done();
|
||||
}).catch(err => {
|
||||
_app.error('Error recursively removing directory ' + dir + '\n' + util.format(err), Object.assign({
|
||||
key: 'rmdir-' + dir
|
||||
}, options));
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
promiseEach(Object.keys(children), file => {
|
||||
if (children[file]) {
|
||||
return functions.rmdir(file, options);
|
||||
} else {
|
||||
return functions.unlink(file, options);
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
_app.error(
|
||||
'Error recursively removing directory ' +
|
||||
dir +
|
||||
'\n' +
|
||||
util.format(err),
|
||||
Object.assign(
|
||||
{
|
||||
key: 'rmdir-' + dir,
|
||||
},
|
||||
options
|
||||
)
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}),
|
||||
};
|
||||
|
||||
module.exports = app => {
|
||||
_app = app;
|
||||
return functions;
|
||||
_app = app;
|
||||
return functions;
|
||||
};
|
||||
|
|
|
|||
219
src/json.js
219
src/json.js
|
|
@ -7,7 +7,7 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const util = require('util');
|
||||
|
|
@ -23,121 +23,126 @@ let imported = Object.create(null);
|
|||
* @param {*} [hash] Hash of previously loaded file. If hashes match, load will be aborted
|
||||
* @returns {Promise}
|
||||
*/
|
||||
module.exports = (app, file, hash) => new Promise((fulfill, reject) => {
|
||||
let newHash = null,
|
||||
result;
|
||||
module.exports = (app, file, hash) =>
|
||||
new Promise((fulfill, reject) => {
|
||||
let newHash = null,
|
||||
result;
|
||||
|
||||
/**
|
||||
* Parse json using JSONStream library
|
||||
*
|
||||
* @param JSONStream
|
||||
* @param es
|
||||
*/
|
||||
function parseStream(JSONStream, es) {
|
||||
let stream = fs.createReadStream(file, 'utf8'),
|
||||
data;
|
||||
/**
|
||||
* Parse json using JSONStream library
|
||||
*
|
||||
* @param JSONStream
|
||||
* @param es
|
||||
*/
|
||||
function parseStream(JSONStream, es) {
|
||||
let stream = fs.createReadStream(file, 'utf8'),
|
||||
data;
|
||||
|
||||
stream.on('error', err => {
|
||||
reject('Error importing ' + file + '\n' + util.format(err));
|
||||
});
|
||||
stream.on('end', () => {
|
||||
result.data = data;
|
||||
fulfill(result);
|
||||
});
|
||||
stream.pipe(JSONStream.parse(true)).pipe(es.mapSync(res => {
|
||||
data = res;
|
||||
}));
|
||||
}
|
||||
stream.on('error', err => {
|
||||
reject('Error importing ' + file + '\n' + util.format(err));
|
||||
});
|
||||
stream.on('end', () => {
|
||||
result.data = data;
|
||||
fulfill(result);
|
||||
});
|
||||
stream.pipe(JSONStream.parse(true)).pipe(
|
||||
es.mapSync(res => {
|
||||
data = res;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Common parser that uses synchronous functions to convert string to object
|
||||
*
|
||||
* @param method
|
||||
*/
|
||||
function syncParser(method) {
|
||||
fs.readFile(file, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
reject('Error importing ' + file + '\n' + util.format(err));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
switch (method) {
|
||||
case 'eval':
|
||||
data = Function('return ' + data)();
|
||||
break;
|
||||
/**
|
||||
* Common parser that uses synchronous functions to convert string to object
|
||||
*
|
||||
* @param method
|
||||
*/
|
||||
function syncParser(method) {
|
||||
fs.readFile(file, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
reject('Error importing ' + file + '\n' + util.format(err));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
switch (method) {
|
||||
case 'eval':
|
||||
data = Function('return ' + data)();
|
||||
break;
|
||||
|
||||
default:
|
||||
data = JSON.parse(data);
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
reject('Error importing ' + file + '\n' + util.format(err));
|
||||
return;
|
||||
}
|
||||
default:
|
||||
data = JSON.parse(data);
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
reject('Error importing ' + file + '\n' + util.format(err));
|
||||
return;
|
||||
}
|
||||
|
||||
result.data = data;
|
||||
fulfill(result);
|
||||
});
|
||||
}
|
||||
result.data = data;
|
||||
fulfill(result);
|
||||
});
|
||||
}
|
||||
|
||||
// Get file information
|
||||
fs.lstat(file, (err, stats) => {
|
||||
if (!err) {
|
||||
// Use file size instead of hash for faster loading
|
||||
// assume json files are same when size is not changed
|
||||
newHash = stats.size;
|
||||
}
|
||||
if (newHash && newHash === hash) {
|
||||
fulfill({
|
||||
changed: false,
|
||||
hash: newHash
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Get file information
|
||||
fs.lstat(file, (err, stats) => {
|
||||
if (!err) {
|
||||
// Use file size instead of hash for faster loading
|
||||
// assume json files are same when size is not changed
|
||||
newHash = stats.size;
|
||||
}
|
||||
if (newHash && newHash === hash) {
|
||||
fulfill({
|
||||
changed: false,
|
||||
hash: newHash,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
result = {
|
||||
changed: true,
|
||||
hash: newHash
|
||||
};
|
||||
result = {
|
||||
changed: true,
|
||||
hash: newHash,
|
||||
};
|
||||
|
||||
// Figure out which parser to use
|
||||
// 'eval' is fastest, but its not safe
|
||||
// 'json' is slower, but might crash when memory limit is low
|
||||
// 'stream' is
|
||||
let parser = 'parse';
|
||||
try {
|
||||
parser = typeof app === 'string' ? app : app.config['json-loader'];
|
||||
} catch(err) {
|
||||
}
|
||||
// Figure out which parser to use
|
||||
// 'eval' is fastest, but its not safe
|
||||
// 'json' is slower, but might crash when memory limit is low
|
||||
// 'stream' is
|
||||
let parser = 'parse';
|
||||
try {
|
||||
parser =
|
||||
typeof app === 'string' ? app : app.config['json-loader'];
|
||||
} catch (err) {}
|
||||
|
||||
switch (parser) {
|
||||
case 'stream':
|
||||
// use stream
|
||||
if (imported.JSONStream === void 0) {
|
||||
try {
|
||||
imported.JSONStream = require('JSONStream');
|
||||
imported.eventStream = require('event-stream');
|
||||
} catch (err) {
|
||||
console.error('Cannot use stream JSON parser because JSONStream or event-stream module is not available. Switching to default parser.');
|
||||
imported.JSONStream = null;
|
||||
}
|
||||
}
|
||||
switch (parser) {
|
||||
case 'stream':
|
||||
// use stream
|
||||
if (imported.JSONStream === void 0) {
|
||||
try {
|
||||
imported.JSONStream = require('JSONStream');
|
||||
imported.eventStream = require('event-stream');
|
||||
} catch (err) {
|
||||
console.error(
|
||||
'Cannot use stream JSON parser because JSONStream or event-stream module is not available. Switching to default parser.'
|
||||
);
|
||||
imported.JSONStream = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (imported.JSONStream === null) {
|
||||
syncParser('json');
|
||||
} else {
|
||||
parseStream(imported.JSONStream, imported.eventStream);
|
||||
}
|
||||
break;
|
||||
if (imported.JSONStream === null) {
|
||||
syncParser('json');
|
||||
} else {
|
||||
parseStream(imported.JSONStream, imported.eventStream);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'eval':
|
||||
// use Function()
|
||||
syncParser('eval');
|
||||
break;
|
||||
case 'eval':
|
||||
// use Function()
|
||||
syncParser('eval');
|
||||
break;
|
||||
|
||||
default:
|
||||
// use JSON.parse()
|
||||
syncParser('json');
|
||||
}
|
||||
});
|
||||
});
|
||||
default:
|
||||
// use JSON.parse()
|
||||
syncParser('json');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
155
src/log.js
155
src/log.js
|
|
@ -7,22 +7,22 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
const util = require('util');
|
||||
|
||||
const defaultOptions = {
|
||||
// True if message should be copied to stdout or stderr
|
||||
log: true,
|
||||
// True if message should be copied to stdout or stderr
|
||||
log: true,
|
||||
|
||||
// Logger object for event logging (combines multiple messages for one big log)
|
||||
logger: null,
|
||||
// Logger object for event logging (combines multiple messages for one big log)
|
||||
logger: null,
|
||||
|
||||
// Unique key. If set, message with that key will be sent by mail only once. Used to avoid sending too many emails
|
||||
key: null,
|
||||
// Unique key. If set, message with that key will be sent by mail only once. Used to avoid sending too many emails
|
||||
key: null,
|
||||
|
||||
// Console object
|
||||
console: console
|
||||
// Console object
|
||||
console: console,
|
||||
};
|
||||
|
||||
// List of notices that are sent only once per session
|
||||
|
|
@ -37,9 +37,9 @@ let throttled = null;
|
|||
* @param app
|
||||
*/
|
||||
const sendQueue = app => {
|
||||
let text = throttled.join('\n\n- - - - - - - - - - -\n\n');
|
||||
throttled = null;
|
||||
app.mail(text);
|
||||
let text = throttled.join('\n\n- - - - - - - - - - -\n\n');
|
||||
throttled = null;
|
||||
app.mail(text);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -54,71 +54,86 @@ const sendQueue = app => {
|
|||
* @param {object|boolean} [options]
|
||||
*/
|
||||
module.exports = (app, error, message, options) => {
|
||||
options = Object.assign(Object.create(null), defaultOptions, options === void 0 ? Object.create(null) : (typeof options === 'boolean' ? {
|
||||
log: options
|
||||
}: options));
|
||||
options = Object.assign(
|
||||
Object.create(null),
|
||||
defaultOptions,
|
||||
options === void 0
|
||||
? Object.create(null)
|
||||
: typeof options === 'boolean'
|
||||
? {
|
||||
log: options,
|
||||
}
|
||||
: options
|
||||
);
|
||||
|
||||
// Convert to test
|
||||
if (typeof message !== 'string') {
|
||||
message = util.format(message);
|
||||
}
|
||||
// Convert to test
|
||||
if (typeof message !== 'string') {
|
||||
message = util.format(message);
|
||||
}
|
||||
|
||||
// Get time stamp
|
||||
let time = new Date();
|
||||
time = (time.getUTCHours() > 9 ? '[' : '[0') + time.getUTCHours() + (time.getUTCMinutes() > 9 ? ':' : ':0') + time.getUTCMinutes() + (time.getUTCSeconds() > 9 ? ':' : ':0') + time.getUTCSeconds() + '] ';
|
||||
// Get time stamp
|
||||
let time = new Date();
|
||||
time =
|
||||
(time.getUTCHours() > 9 ? '[' : '[0') +
|
||||
time.getUTCHours() +
|
||||
(time.getUTCMinutes() > 9 ? ':' : ':0') +
|
||||
time.getUTCMinutes() +
|
||||
(time.getUTCSeconds() > 9 ? ':' : ':0') +
|
||||
time.getUTCSeconds() +
|
||||
'] ';
|
||||
|
||||
// Copy message to console
|
||||
if (options.log || !app.mail) {
|
||||
if (error) {
|
||||
options.console.error(time + '\x1b[31m' + message + '\x1b[0m');
|
||||
} else {
|
||||
options.console.log(time + message);
|
||||
}
|
||||
}
|
||||
// Copy message to console
|
||||
if (options.log || !app.mail) {
|
||||
if (error) {
|
||||
options.console.error(time + '\x1b[31m' + message + '\x1b[0m');
|
||||
} else {
|
||||
options.console.log(time + message);
|
||||
}
|
||||
}
|
||||
|
||||
if (!app.mail) {
|
||||
return;
|
||||
}
|
||||
message = time + message;
|
||||
if (!app.mail) {
|
||||
return;
|
||||
}
|
||||
message = time + message;
|
||||
|
||||
// Copy to mail logger
|
||||
if (options.logger) {
|
||||
options.logger[error ? 'error' : 'log'](message);
|
||||
return;
|
||||
}
|
||||
// Copy to mail logger
|
||||
if (options.logger) {
|
||||
options.logger[error ? 'error' : 'log'](message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send email if its a error and has not been sent before
|
||||
if (!error) {
|
||||
return;
|
||||
}
|
||||
if (options.key) {
|
||||
let time = Date.now() / 1000,
|
||||
repeat;
|
||||
// Send email if its a error and has not been sent before
|
||||
if (!error) {
|
||||
return;
|
||||
}
|
||||
if (options.key) {
|
||||
let time = Date.now() / 1000,
|
||||
repeat;
|
||||
|
||||
try {
|
||||
repeat = app.config.mail.repeat;
|
||||
} catch (err) {
|
||||
repeat = 0;
|
||||
}
|
||||
try {
|
||||
repeat = app.config.mail.repeat;
|
||||
} catch (err) {
|
||||
repeat = 0;
|
||||
}
|
||||
|
||||
if (logged[options.key]) {
|
||||
if (!repeat || logged[options.key] > time) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
logged[options.key] = repeat ? time + repeat : true;
|
||||
}
|
||||
if (logged[options.key]) {
|
||||
if (!repeat || logged[options.key] > time) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
logged[options.key] = repeat ? time + repeat : true;
|
||||
}
|
||||
|
||||
// Add message to throttled data
|
||||
if (throttled === null) {
|
||||
throttled = [];
|
||||
let delay;
|
||||
try {
|
||||
delay = app.config.mail.throttle;
|
||||
} catch (err) {
|
||||
delay = 60;
|
||||
}
|
||||
setTimeout(sendQueue.bind(null, app), delay * 1000)
|
||||
}
|
||||
throttled.push(message);
|
||||
// Add message to throttled data
|
||||
if (throttled === null) {
|
||||
throttled = [];
|
||||
let delay;
|
||||
try {
|
||||
delay = app.config.mail.throttle;
|
||||
} catch (err) {
|
||||
delay = 60;
|
||||
}
|
||||
setTimeout(sendQueue.bind(null, app), delay * 1000);
|
||||
}
|
||||
throttled.push(message);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,47 +7,51 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
class Logger {
|
||||
constructor(app, subject, delay) {
|
||||
this.app = app;
|
||||
this.subject = subject;
|
||||
this.messages = [];
|
||||
this.delay = typeof delay === 'number' ? Math.min(Math.max(delay, 15), 300) : 60;
|
||||
this.throttled = false;
|
||||
}
|
||||
constructor(app, subject, delay) {
|
||||
this.app = app;
|
||||
this.subject = subject;
|
||||
this.messages = [];
|
||||
this.delay =
|
||||
typeof delay === 'number' ? Math.min(Math.max(delay, 15), 300) : 60;
|
||||
this.throttled = false;
|
||||
}
|
||||
|
||||
send() {
|
||||
if (this.messages.length) {
|
||||
this.app.mail((this.subject ? this.subject + '\n\n' : '') + this.messages.join('\n'));
|
||||
this.messages = [];
|
||||
}
|
||||
}
|
||||
send() {
|
||||
if (this.messages.length) {
|
||||
this.app.mail(
|
||||
(this.subject ? this.subject + '\n\n' : '') +
|
||||
this.messages.join('\n')
|
||||
);
|
||||
this.messages = [];
|
||||
}
|
||||
}
|
||||
|
||||
queue() {
|
||||
if (!this.throttled) {
|
||||
this.throttled = true;
|
||||
setTimeout(() => {
|
||||
this.send();
|
||||
this.throttled = false;
|
||||
}, this.delay * 1000);
|
||||
}
|
||||
}
|
||||
queue() {
|
||||
if (!this.throttled) {
|
||||
this.throttled = true;
|
||||
setTimeout(() => {
|
||||
this.send();
|
||||
this.throttled = false;
|
||||
}, this.delay * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
log(message) {
|
||||
this.messages.push(message);
|
||||
if (!this.throttled) {
|
||||
this.queue();
|
||||
}
|
||||
}
|
||||
log(message) {
|
||||
this.messages.push(message);
|
||||
if (!this.throttled) {
|
||||
this.queue();
|
||||
}
|
||||
}
|
||||
|
||||
error(message) {
|
||||
this.messages.push(message);
|
||||
if (!this.throttled) {
|
||||
this.queue();
|
||||
}
|
||||
}
|
||||
error(message) {
|
||||
this.messages.push(message);
|
||||
if (!this.throttled) {
|
||||
this.queue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
62
src/mail.js
62
src/mail.js
|
|
@ -7,44 +7,44 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
let nodemailer;
|
||||
|
||||
module.exports = (app, message) => {
|
||||
if (nodemailer === null) {
|
||||
return;
|
||||
}
|
||||
if (nodemailer === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let config;
|
||||
try {
|
||||
config = app.config.mail;
|
||||
if (!config.active) {
|
||||
return;
|
||||
}
|
||||
let config;
|
||||
try {
|
||||
config = app.config.mail;
|
||||
if (!config.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (nodemailer === void 0) {
|
||||
nodemailer = require('nodemailer');
|
||||
}
|
||||
} catch (err) {
|
||||
nodemailer = null;
|
||||
return;
|
||||
}
|
||||
if (nodemailer === void 0) {
|
||||
nodemailer = require('nodemailer');
|
||||
}
|
||||
} catch (err) {
|
||||
nodemailer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
let transporter = nodemailer.createTransport(config.transport);
|
||||
let transporter = nodemailer.createTransport(config.transport);
|
||||
|
||||
// Set data
|
||||
let mailOptions = {
|
||||
from: config.from,
|
||||
to: config.to,
|
||||
subject: config.subject,
|
||||
text: message
|
||||
};
|
||||
// Set data
|
||||
let mailOptions = {
|
||||
from: config.from,
|
||||
to: config.to,
|
||||
subject: config.subject,
|
||||
text: message,
|
||||
};
|
||||
|
||||
// Send email
|
||||
transporter.sendMail(mailOptions, (err, info) => {
|
||||
if (err) {
|
||||
console.error('Error sending mail:', err);
|
||||
}
|
||||
});
|
||||
// Send email
|
||||
transporter.sendMail(mailOptions, (err, info) => {
|
||||
if (err) {
|
||||
console.error('Error sending mail:', err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Alternative to Promise.all() that runs each promise after another, not simultaneously
|
||||
|
|
@ -16,31 +16,34 @@
|
|||
* @param callback
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
module.exports = (list, callback) => new Promise((fulfill, reject) => {
|
||||
let results = [],
|
||||
index = -1,
|
||||
total = list.length;
|
||||
module.exports = (list, callback) =>
|
||||
new Promise((fulfill, reject) => {
|
||||
let results = [],
|
||||
index = -1,
|
||||
total = list.length;
|
||||
|
||||
function next() {
|
||||
index ++;
|
||||
if (index === total) {
|
||||
fulfill(results);
|
||||
return;
|
||||
}
|
||||
function next() {
|
||||
index++;
|
||||
if (index === total) {
|
||||
fulfill(results);
|
||||
return;
|
||||
}
|
||||
|
||||
let promise = callback(list[index]);
|
||||
if (promise === null) {
|
||||
// skip
|
||||
next();
|
||||
return;
|
||||
}
|
||||
promise.then(result => {
|
||||
results.push(result);
|
||||
next();
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
})
|
||||
}
|
||||
let promise = callback(list[index]);
|
||||
if (promise === null) {
|
||||
// skip
|
||||
next();
|
||||
return;
|
||||
}
|
||||
promise
|
||||
.then(result => {
|
||||
results.push(result);
|
||||
next();
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
next();
|
||||
});
|
||||
|
|
|
|||
674
src/reload.js
674
src/reload.js
|
|
@ -7,7 +7,7 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const util = require('util');
|
||||
|
|
@ -15,238 +15,340 @@ const promiseEach = require('./promise');
|
|||
const Collection = require('@iconify/json-tools').Collection;
|
||||
|
||||
const defaultOptions = {
|
||||
// Logger instance
|
||||
logger: null
|
||||
// Logger instance
|
||||
logger: null,
|
||||
};
|
||||
|
||||
let repoItems = Object.create(null),
|
||||
collectionRepos = Object.create(null),
|
||||
hashes = Object.create(null),
|
||||
nextReload = 0;
|
||||
collectionRepos = Object.create(null),
|
||||
hashes = Object.create(null),
|
||||
nextReload = 0;
|
||||
|
||||
class Loader {
|
||||
constructor(app, repos, options) {
|
||||
this.app = app;
|
||||
this.repos = repos;
|
||||
this.options = options;
|
||||
this.updated = [];
|
||||
this.start = Date.now();
|
||||
this.reloadInfo = false;
|
||||
}
|
||||
constructor(app, repos, options) {
|
||||
this.app = app;
|
||||
this.repos = repos;
|
||||
this.options = options;
|
||||
this.updated = [];
|
||||
this.start = Date.now();
|
||||
this.reloadInfo = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove root directory from filename
|
||||
*
|
||||
* @param {string} filename
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
_prettyFile(filename) {
|
||||
return filename.slice(0, this.app.root.length) === this.app.root ? filename.slice(this.app.root.length + 1) : filename;
|
||||
}
|
||||
/**
|
||||
* Remove root directory from filename
|
||||
*
|
||||
* @param {string} filename
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
_prettyFile(filename) {
|
||||
return filename.slice(0, this.app.root.length) === this.app.root
|
||||
? filename.slice(this.app.root.length + 1)
|
||||
: filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find collections
|
||||
*
|
||||
* @return {Promise<Array>}
|
||||
*/
|
||||
findCollections() {
|
||||
return new Promise((fulfill, reject) => {
|
||||
promiseEach(this.repos, repo => new Promise((fulfill, reject) => {
|
||||
// Get directory
|
||||
let dir = this.app.dirs.iconsDir(repo);
|
||||
if (dir === '') {
|
||||
reject('Missing directory for repository "' + repo + '"');
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Find collections
|
||||
*
|
||||
* @return {Promise<Array>}
|
||||
*/
|
||||
findCollections() {
|
||||
return new Promise((fulfill, reject) => {
|
||||
promiseEach(
|
||||
this.repos,
|
||||
repo =>
|
||||
new Promise((fulfill, reject) => {
|
||||
// Get directory
|
||||
let dir = this.app.dirs.iconsDir(repo);
|
||||
if (dir === '') {
|
||||
reject(
|
||||
'Missing directory for repository "' +
|
||||
repo +
|
||||
'"'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find all files
|
||||
fs.readdir(dir, (err, files) => {
|
||||
let items = [];
|
||||
if (err) {
|
||||
reject('Error reading directory: ' + this._prettyFile(dir) + '\n' + util.format(err));
|
||||
return;
|
||||
}
|
||||
files.forEach(file => {
|
||||
if (file.slice(-5) !== '.json') {
|
||||
return;
|
||||
}
|
||||
items.push({
|
||||
repo: repo,
|
||||
file: file,
|
||||
filename: dir + '/' + file,
|
||||
prefix: file.slice(0, file.length - 5)
|
||||
});
|
||||
});
|
||||
fulfill(items);
|
||||
});
|
||||
})).then(results => {
|
||||
let items = [];
|
||||
// Find all files
|
||||
fs.readdir(dir, (err, files) => {
|
||||
let items = [];
|
||||
if (err) {
|
||||
reject(
|
||||
'Error reading directory: ' +
|
||||
this._prettyFile(dir) +
|
||||
'\n' +
|
||||
util.format(err)
|
||||
);
|
||||
return;
|
||||
}
|
||||
files.forEach(file => {
|
||||
if (file.slice(-5) !== '.json') {
|
||||
return;
|
||||
}
|
||||
items.push({
|
||||
repo: repo,
|
||||
file: file,
|
||||
filename: dir + '/' + file,
|
||||
prefix: file.slice(0, file.length - 5),
|
||||
});
|
||||
});
|
||||
fulfill(items);
|
||||
});
|
||||
})
|
||||
)
|
||||
.then(results => {
|
||||
let items = [];
|
||||
|
||||
results.forEach(result => {
|
||||
result.forEach(item => {
|
||||
if (collectionRepos[item.prefix] === void 0) {
|
||||
// New collection. Add it to list
|
||||
if (repoItems[item.repo] === void 0) {
|
||||
repoItems[item.repo] = [item.prefix];
|
||||
} else {
|
||||
repoItems[item.repo].push(item.prefix);
|
||||
}
|
||||
items.push(item);
|
||||
return;
|
||||
}
|
||||
results.forEach(result => {
|
||||
result.forEach(item => {
|
||||
if (collectionRepos[item.prefix] === void 0) {
|
||||
// New collection. Add it to list
|
||||
if (repoItems[item.repo] === void 0) {
|
||||
repoItems[item.repo] = [item.prefix];
|
||||
} else {
|
||||
repoItems[item.repo].push(item.prefix);
|
||||
}
|
||||
items.push(item);
|
||||
return;
|
||||
}
|
||||
|
||||
if (collectionRepos[item.prefix] !== item.repo) {
|
||||
// Conflict: same prefix in multiple repositories
|
||||
this.app.error('Collection "' + item.prefix + '" is found in multiple repositories. Ignoring json file from ' + item.repo + ', using file from ' + collectionRepos[item.prefix], Object.assign({
|
||||
key: 'json-duplicate/' + item.repo + '/' + item.prefix
|
||||
}, this.options));
|
||||
return;
|
||||
}
|
||||
if (collectionRepos[item.prefix] !== item.repo) {
|
||||
// Conflict: same prefix in multiple repositories
|
||||
this.app.error(
|
||||
'Collection "' +
|
||||
item.prefix +
|
||||
'" is found in multiple repositories. Ignoring json file from ' +
|
||||
item.repo +
|
||||
', using file from ' +
|
||||
collectionRepos[item.prefix],
|
||||
Object.assign(
|
||||
{
|
||||
key:
|
||||
'json-duplicate/' +
|
||||
item.repo +
|
||||
'/' +
|
||||
item.prefix,
|
||||
},
|
||||
this.options
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Everything is fine
|
||||
items.push(item);
|
||||
});
|
||||
// Everything is fine
|
||||
items.push(item);
|
||||
});
|
||||
});
|
||||
fulfill(items);
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
fulfill(items);
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Load collections
|
||||
*
|
||||
* @param {Array} items
|
||||
* @return {Promise<any>}
|
||||
*/
|
||||
loadCollections(items) {
|
||||
return new Promise((fulfill, reject) => {
|
||||
let total;
|
||||
|
||||
/**
|
||||
* Load collections
|
||||
*
|
||||
* @param {Array} items
|
||||
* @return {Promise<any>}
|
||||
*/
|
||||
loadCollections(items) {
|
||||
return new Promise((fulfill, reject) => {
|
||||
let total;
|
||||
// Load all files
|
||||
promiseEach(
|
||||
items,
|
||||
item =>
|
||||
new Promise((fulfill, reject) => {
|
||||
let collection;
|
||||
|
||||
// Load all files
|
||||
promiseEach(items, item => new Promise((fulfill, reject) => {
|
||||
let collection;
|
||||
// Load JSON file
|
||||
this.app
|
||||
.loadJSON(
|
||||
item.filename,
|
||||
hashes[item.prefix] === void 0
|
||||
? null
|
||||
: hashes[item.prefix]
|
||||
)
|
||||
.then(result => {
|
||||
if (!result.changed) {
|
||||
// Nothing to do
|
||||
fulfill(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Load JSON file
|
||||
this.app.loadJSON(item.filename, hashes[item.prefix] === void 0 ? null : hashes[item.prefix]).then(result => {
|
||||
if (!result.changed) {
|
||||
// Nothing to do
|
||||
fulfill(true);
|
||||
return;
|
||||
}
|
||||
return this.loadCollection(item, result);
|
||||
})
|
||||
.then(result => {
|
||||
collection = result;
|
||||
|
||||
return this.loadCollection(item, result);
|
||||
}).then(result => {
|
||||
collection = result;
|
||||
// Run post-load function if there is one
|
||||
if (this.app.postLoadCollection) {
|
||||
return this.app.postLoadCollection(
|
||||
collection,
|
||||
this.options
|
||||
);
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
fulfill(collection);
|
||||
})
|
||||
.catch(err => {
|
||||
reject(
|
||||
'Error loading json file: ' +
|
||||
this._prettyFile(item.filename) +
|
||||
'\n' +
|
||||
util.format(err)
|
||||
);
|
||||
});
|
||||
})
|
||||
)
|
||||
.then(collections => {
|
||||
let loaded = 0,
|
||||
skipped = 0;
|
||||
|
||||
// Run post-load function if there is one
|
||||
if (this.app.postLoadCollection) {
|
||||
return this.app.postLoadCollection(collection, this.options);
|
||||
}
|
||||
}).then(() => {
|
||||
fulfill(collection);
|
||||
}).catch(err => {
|
||||
reject('Error loading json file: ' + this._prettyFile(item.filename) + '\n' + util.format(err));
|
||||
});
|
||||
total = 0;
|
||||
collections.forEach(collection => {
|
||||
if (collection === true) {
|
||||
skipped++;
|
||||
return;
|
||||
}
|
||||
loaded++;
|
||||
|
||||
})).then(collections => {
|
||||
let loaded = 0,
|
||||
skipped = 0;
|
||||
let count = Object.keys(collection.items.icons).length,
|
||||
prefix = collection.prefix();
|
||||
|
||||
total = 0;
|
||||
collections.forEach(collection => {
|
||||
if (collection === true) {
|
||||
skipped ++;
|
||||
return;
|
||||
}
|
||||
loaded ++;
|
||||
this.app.log(
|
||||
'Loaded collection ' +
|
||||
prefix +
|
||||
' from ' +
|
||||
collection.filename +
|
||||
' (' +
|
||||
count +
|
||||
' icons)',
|
||||
this.options
|
||||
);
|
||||
total += count;
|
||||
this.app.collections[prefix] = collection;
|
||||
});
|
||||
this.app.log(
|
||||
'Loaded ' +
|
||||
total +
|
||||
' icons from ' +
|
||||
loaded +
|
||||
(loaded > 1 ? ' collections ' : ' collection ') +
|
||||
(skipped
|
||||
? '(no changes in ' +
|
||||
skipped +
|
||||
(skipped > 1
|
||||
? ' collections) '
|
||||
: ' collection) ')
|
||||
: '') +
|
||||
'in ' +
|
||||
(Date.now() - this.start) / 1000 +
|
||||
' seconds.',
|
||||
this.options
|
||||
);
|
||||
|
||||
let count = Object.keys(collection.items.icons).length,
|
||||
prefix = collection.prefix();
|
||||
if (this.reloadInfo) {
|
||||
return this.getCollectionsJSON();
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
fulfill(total);
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.app.log('Loaded collection ' + prefix + ' from ' + collection.filename + ' (' + count + ' icons)', this.options);
|
||||
total += count;
|
||||
this.app.collections[prefix] = collection;
|
||||
});
|
||||
this.app.log('Loaded ' + total + ' icons from ' + loaded + (loaded > 1 ? ' collections ' : ' collection ') + (skipped ? '(no changes in ' + skipped + (skipped > 1 ? ' collections) ' : ' collection) ') : '') + 'in ' + (Date.now() - this.start) / 1000 + ' seconds.', this.options);
|
||||
/**
|
||||
* Get Iconify collections data
|
||||
*
|
||||
* @return {Promise<any>}
|
||||
*/
|
||||
getCollectionsJSON() {
|
||||
return new Promise((fulfill, reject) => {
|
||||
let filename =
|
||||
this.app.dirs.rootDir('iconify') + '/collections.json';
|
||||
fs.readFile(filename, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
reject(
|
||||
'Error locating collections.json for Iconify default icons.\n' +
|
||||
util.format(err)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.reloadInfo) {
|
||||
return this.getCollectionsJSON();
|
||||
}
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
} catch (err) {
|
||||
reject(
|
||||
'Error reading contents of' +
|
||||
filename +
|
||||
'\n' +
|
||||
util.format(err)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
}).then(() => {
|
||||
fulfill(total);
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
this.app.collectionsJSON = data;
|
||||
fulfill();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Iconify collections data
|
||||
*
|
||||
* @return {Promise<any>}
|
||||
*/
|
||||
getCollectionsJSON() {
|
||||
return new Promise((fulfill, reject) => {
|
||||
let filename = this.app.dirs.rootDir('iconify') + '/collections.json';
|
||||
fs.readFile(filename, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
reject('Error locating collections.json for Iconify default icons.\n' + util.format(err));
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Load one collection
|
||||
*
|
||||
* @param {object} item findCollections() result
|
||||
* @param {object} data loadJSON() result
|
||||
* @return {Promise<Collection>}
|
||||
*/
|
||||
loadCollection(item, data) {
|
||||
return new Promise((fulfill, reject) => {
|
||||
let collection = new Collection();
|
||||
if (!collection.loadJSON(data.data, item.prefix)) {
|
||||
delete data.data;
|
||||
reject(
|
||||
'Error loading collection "' +
|
||||
item.prefix +
|
||||
'" from repository "' +
|
||||
item.repo +
|
||||
'": error parsing JSON'
|
||||
);
|
||||
return;
|
||||
}
|
||||
delete data.data;
|
||||
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
} catch (err) {
|
||||
reject('Error reading contents of' + filename + '\n' + util.format(err));
|
||||
return;
|
||||
}
|
||||
let prefix = collection.prefix();
|
||||
if (prefix !== item.prefix) {
|
||||
delete collection.items;
|
||||
reject(
|
||||
'Error loading collection "' +
|
||||
item.prefix +
|
||||
'" from repository "' +
|
||||
item.repo +
|
||||
'": invalid prefix in JSON file: ' +
|
||||
prefix
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.app.collectionsJSON = data;
|
||||
fulfill();
|
||||
});
|
||||
});
|
||||
}
|
||||
collection.filename = this._prettyFile(item.filename);
|
||||
collection.repo = item.repo;
|
||||
hashes[item.prefix] = data.hash;
|
||||
this.updated.push(item.prefix);
|
||||
if (item.repo === 'iconify') {
|
||||
this.reloadInfo = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load one collection
|
||||
*
|
||||
* @param {object} item findCollections() result
|
||||
* @param {object} data loadJSON() result
|
||||
* @return {Promise<Collection>}
|
||||
*/
|
||||
loadCollection(item, data) {
|
||||
return new Promise((fulfill, reject) => {
|
||||
let collection = new Collection();
|
||||
if (!collection.loadJSON(data.data, item.prefix)) {
|
||||
delete data.data;
|
||||
reject('Error loading collection "' + item.prefix + '" from repository "' + item.repo + '": error parsing JSON');
|
||||
return;
|
||||
}
|
||||
delete data.data;
|
||||
|
||||
let prefix = collection.prefix();
|
||||
if (prefix !== item.prefix) {
|
||||
delete collection.items;
|
||||
reject('Error loading collection "' + item.prefix + '" from repository "' + item.repo + '": invalid prefix in JSON file: ' + prefix);
|
||||
return;
|
||||
}
|
||||
|
||||
collection.filename = this._prettyFile(item.filename);
|
||||
collection.repo = item.repo;
|
||||
hashes[item.prefix] = data.hash;
|
||||
this.updated.push(item.prefix);
|
||||
if (item.repo === 'iconify') {
|
||||
this.reloadInfo = true;
|
||||
}
|
||||
|
||||
fulfill(collection);
|
||||
});
|
||||
}
|
||||
fulfill(collection);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -256,89 +358,99 @@ class Loader {
|
|||
* @param {Array|string|boolean} [repos] Repositories to reload
|
||||
* @param {object} [options]
|
||||
*/
|
||||
module.exports = (app, repos, options) => new Promise((fulfill, reject) => {
|
||||
// Options
|
||||
options = Object.assign(Object.create(null), defaultOptions, typeof options === 'object' ? options : Object.create(null));
|
||||
module.exports = (app, repos, options) =>
|
||||
new Promise((fulfill, reject) => {
|
||||
// Options
|
||||
options = Object.assign(
|
||||
Object.create(null),
|
||||
defaultOptions,
|
||||
typeof options === 'object' ? options : Object.create(null)
|
||||
);
|
||||
|
||||
// Get list of repositories to reload
|
||||
let availableRepos = app.dirs.getRepos();
|
||||
// noinspection FallThroughInSwitchStatementJS
|
||||
switch (typeof repos) {
|
||||
case 'string':
|
||||
if (availableRepos.indexOf(repos) === -1) {
|
||||
reject('Cannot update repository: ' + repos);
|
||||
return;
|
||||
}
|
||||
repos = [repos];
|
||||
break;
|
||||
// Get list of repositories to reload
|
||||
let availableRepos = app.dirs.getRepos();
|
||||
// noinspection FallThroughInSwitchStatementJS
|
||||
switch (typeof repos) {
|
||||
case 'string':
|
||||
if (availableRepos.indexOf(repos) === -1) {
|
||||
reject('Cannot update repository: ' + repos);
|
||||
return;
|
||||
}
|
||||
repos = [repos];
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
if (repos instanceof Array) {
|
||||
let newList = [];
|
||||
repos.forEach(repo => {
|
||||
if (availableRepos.indexOf(repo) !== -1) {
|
||||
newList.push(repo);
|
||||
}
|
||||
});
|
||||
repos = newList;
|
||||
break;
|
||||
}
|
||||
case 'object':
|
||||
if (repos instanceof Array) {
|
||||
let newList = [];
|
||||
repos.forEach(repo => {
|
||||
if (availableRepos.indexOf(repo) !== -1) {
|
||||
newList.push(repo);
|
||||
}
|
||||
});
|
||||
repos = newList;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'boolean':
|
||||
if (repos === false) {
|
||||
// false -> reload was called by /reload url
|
||||
// limit such reloads to 1 per 30 seconds
|
||||
if (Date.now() < nextReload) {
|
||||
fulfill(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
case 'boolean':
|
||||
if (repos === false) {
|
||||
// false -> reload was called by /reload url
|
||||
// limit such reloads to 1 per 30 seconds
|
||||
if (Date.now() < nextReload) {
|
||||
fulfill(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
repos = availableRepos.slice(0);
|
||||
}
|
||||
default:
|
||||
repos = availableRepos.slice(0);
|
||||
}
|
||||
|
||||
if (!repos.length) {
|
||||
reject('No available repositories to update.');
|
||||
return;
|
||||
}
|
||||
if (!repos.length) {
|
||||
reject('No available repositories to update.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (app.reloading === true) {
|
||||
reject('Reload is already in progress.');
|
||||
return;
|
||||
}
|
||||
if (app.reloading === true) {
|
||||
reject('Reload is already in progress.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create logger if its missing
|
||||
if (!options.logger) {
|
||||
options.logger = app.logger('Loading repositories', 30);
|
||||
}
|
||||
// Create logger if its missing
|
||||
if (!options.logger) {
|
||||
options.logger = app.logger('Loading repositories', 30);
|
||||
}
|
||||
|
||||
// Create loader instance and do stuff
|
||||
let loader = new Loader(app, repos, options),
|
||||
count;
|
||||
// Create loader instance and do stuff
|
||||
let loader = new Loader(app, repos, options),
|
||||
count;
|
||||
|
||||
app.reloading = true;
|
||||
loader.findCollections().then(items => {
|
||||
return loader.loadCollections(items);
|
||||
}).then(total => {
|
||||
count = total;
|
||||
app.reloading = true;
|
||||
loader
|
||||
.findCollections()
|
||||
.then(items => {
|
||||
return loader.loadCollections(items);
|
||||
})
|
||||
.then(total => {
|
||||
count = total;
|
||||
|
||||
// Run post-load function if there is one
|
||||
if (app.postReload) {
|
||||
return app.postReload(loader.updated, options);
|
||||
}
|
||||
}).then(() => {
|
||||
// Do not allow /reload for 30 seconds
|
||||
nextReload = Date.now() + 30000;
|
||||
// Run post-load function if there is one
|
||||
if (app.postReload) {
|
||||
return app.postReload(loader.updated, options);
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
// Do not allow /reload for 30 seconds
|
||||
nextReload = Date.now() + 30000;
|
||||
|
||||
// Done
|
||||
fulfill({
|
||||
icons: count,
|
||||
updated: loader.updated
|
||||
});
|
||||
app.reloading = false;
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
app.reloading = false;
|
||||
});
|
||||
});
|
||||
// Done
|
||||
fulfill({
|
||||
icons: count,
|
||||
updated: loader.updated,
|
||||
});
|
||||
app.reloading = false;
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err);
|
||||
app.reloading = false;
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
const SVG = require('@iconify/json-tools').SVG;
|
||||
|
||||
|
|
@ -19,8 +19,8 @@ const SVG = require('@iconify/json-tools').SVG;
|
|||
* @returns {string}
|
||||
*/
|
||||
function generateSVG(icon, params) {
|
||||
let svg = new SVG(icon);
|
||||
return svg.getSVG(params);
|
||||
let svg = new SVG(icon);
|
||||
return svg.getSVG(params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -42,54 +42,57 @@ const _callbackMatch = /^[a-z0-9_.]+$/i;
|
|||
* @param {string} ext Extension
|
||||
*/
|
||||
module.exports = (app, req, res, prefix, query, ext) => {
|
||||
if (app.collections[prefix] === void 0) {
|
||||
app.response(req, res, 404);
|
||||
return;
|
||||
}
|
||||
if (app.collections[prefix] === void 0) {
|
||||
app.response(req, res, 404);
|
||||
return;
|
||||
}
|
||||
|
||||
let collection = app.collections[prefix],
|
||||
params = req.query;
|
||||
let collection = app.collections[prefix],
|
||||
params = req.query;
|
||||
|
||||
let parse = () => {
|
||||
switch (ext) {
|
||||
case 'svg':
|
||||
// Generate SVG
|
||||
// query = icon name
|
||||
let icon = collection.getIconData(query);
|
||||
if (icon === null) {
|
||||
return 404;
|
||||
}
|
||||
return {
|
||||
filename: query + '.svg',
|
||||
type: 'image/svg+xml; charset=utf-8',
|
||||
body: generateSVG(icon, params)
|
||||
};
|
||||
let parse = () => {
|
||||
switch (ext) {
|
||||
case 'svg':
|
||||
// Generate SVG
|
||||
// query = icon name
|
||||
let icon = collection.getIconData(query);
|
||||
if (icon === null) {
|
||||
return 404;
|
||||
}
|
||||
return {
|
||||
filename: query + '.svg',
|
||||
type: 'image/svg+xml; charset=utf-8',
|
||||
body: generateSVG(icon, params),
|
||||
};
|
||||
|
||||
case 'js':
|
||||
case 'json':
|
||||
if (query !== 'icons' || typeof params.icons !== 'string') {
|
||||
return 404;
|
||||
}
|
||||
case 'js':
|
||||
case 'json':
|
||||
if (query !== 'icons' || typeof params.icons !== 'string') {
|
||||
return 404;
|
||||
}
|
||||
|
||||
let result = collection.getIcons(params.icons.split(','));
|
||||
let result = collection.getIcons(params.icons.split(','));
|
||||
|
||||
if (result === null || !Object.keys(result.icons).length) {
|
||||
return 404;
|
||||
}
|
||||
if (result.aliases !== void 0 && !Object.keys(result.aliases).length) {
|
||||
delete result.aliases;
|
||||
}
|
||||
if (result === null || !Object.keys(result.icons).length) {
|
||||
return 404;
|
||||
}
|
||||
if (
|
||||
result.aliases !== void 0 &&
|
||||
!Object.keys(result.aliases).length
|
||||
) {
|
||||
delete result.aliases;
|
||||
}
|
||||
|
||||
return {
|
||||
js: ext === 'js',
|
||||
defaultCallback: 'SimpleSVG._loaderCallback',
|
||||
data: result
|
||||
};
|
||||
return {
|
||||
js: ext === 'js',
|
||||
defaultCallback: 'SimpleSVG._loaderCallback',
|
||||
data: result,
|
||||
};
|
||||
|
||||
default:
|
||||
return 404;
|
||||
}
|
||||
};
|
||||
default:
|
||||
return 404;
|
||||
}
|
||||
};
|
||||
|
||||
app.response(req, res, parse());
|
||||
app.response(req, res, parse());
|
||||
};
|
||||
|
|
|
|||
121
src/request.js
121
src/request.js
|
|
@ -7,7 +7,7 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Parse request
|
||||
|
|
@ -18,63 +18,80 @@
|
|||
* @param {string} query Query
|
||||
*/
|
||||
module.exports = (app, req, res, query) => {
|
||||
let body;
|
||||
let body;
|
||||
|
||||
switch (query) {
|
||||
case 'version':
|
||||
body = 'Iconify API version ' + app.version + ' (Node';
|
||||
if (app.config.region.length) {
|
||||
body += ', ' + app.config.region;
|
||||
}
|
||||
body += ')';
|
||||
app.response(req, res, {
|
||||
type: 'text/plain',
|
||||
body: body
|
||||
});
|
||||
return;
|
||||
switch (query) {
|
||||
case 'version':
|
||||
body = 'Iconify API version ' + app.version + ' (Node';
|
||||
if (app.config.region.length) {
|
||||
body += ', ' + app.config.region;
|
||||
}
|
||||
body += ')';
|
||||
app.response(req, res, {
|
||||
type: 'text/plain',
|
||||
body: body,
|
||||
});
|
||||
return;
|
||||
|
||||
case 'robots':
|
||||
app.response(req, res, {
|
||||
type: 'text/plain',
|
||||
body: 'User-agent: *\nDisallow: /'
|
||||
});
|
||||
return;
|
||||
case 'robots':
|
||||
app.response(req, res, {
|
||||
type: 'text/plain',
|
||||
body: 'User-agent: *\nDisallow: /',
|
||||
});
|
||||
return;
|
||||
|
||||
case 'reload':
|
||||
// Send 200 response regardless of success to prevent visitors from guessing key
|
||||
app.response(req, res, 200);
|
||||
case 'reload':
|
||||
// Send 200 response regardless of success to prevent visitors from guessing key
|
||||
app.response(req, res, 200);
|
||||
|
||||
// Do stuff
|
||||
if (app.config['reload-secret'].length && req.query && typeof req.query.key === 'string' && req.query.key === app.config['reload-secret'] && !app.reloading) {
|
||||
process.nextTick(() => {
|
||||
app.reload(false).then(() => {
|
||||
}).catch(err => {
|
||||
app.error('Error reloading collections:\n' + util.format(err));
|
||||
});
|
||||
});
|
||||
}
|
||||
return;
|
||||
// Do stuff
|
||||
if (
|
||||
app.config['reload-secret'].length &&
|
||||
req.query &&
|
||||
typeof req.query.key === 'string' &&
|
||||
req.query.key === app.config['reload-secret'] &&
|
||||
!app.reloading
|
||||
) {
|
||||
process.nextTick(() => {
|
||||
app.reload(false)
|
||||
.then(() => {})
|
||||
.catch(err => {
|
||||
app.error(
|
||||
'Error reloading collections:\n' +
|
||||
util.format(err)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
return;
|
||||
|
||||
case 'sync':
|
||||
// Send 200 response regardless of success to prevent visitors from guessing key
|
||||
app.response(req, res, 200);
|
||||
case 'sync':
|
||||
// Send 200 response regardless of success to prevent visitors from guessing key
|
||||
app.response(req, res, 200);
|
||||
|
||||
let repo = req.query.repo;
|
||||
if (typeof repo !== 'string' || !app.config.canSync || !app.config.sync[repo] || !app.config.sync.git || !app.config.sync.secret) {
|
||||
return;
|
||||
}
|
||||
let repo = req.query.repo;
|
||||
if (
|
||||
typeof repo !== 'string' ||
|
||||
!app.config.canSync ||
|
||||
!app.config.sync[repo] ||
|
||||
!app.config.sync.git ||
|
||||
!app.config.sync.secret
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let key = req.query.key;
|
||||
if (key !== app.config.sync.secret) {
|
||||
return;
|
||||
}
|
||||
let key = req.query.key;
|
||||
if (key !== app.config.sync.secret) {
|
||||
return;
|
||||
}
|
||||
|
||||
process.nextTick(() => {
|
||||
app.sync(repo).then(() => {
|
||||
}).catch(err => {
|
||||
app.error(err);
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
process.nextTick(() => {
|
||||
app.sync(repo)
|
||||
.then(() => {})
|
||||
.catch(err => {
|
||||
app.error(err);
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
128
src/response.js
128
src/response.js
|
|
@ -7,7 +7,7 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Regexp for checking callback attribute
|
||||
|
|
@ -26,62 +26,84 @@ const callbackMatch = /^[a-z0-9_.]+$/i;
|
|||
* @param result
|
||||
*/
|
||||
module.exports = (app, req, res, result) => {
|
||||
if (typeof result === 'number') {
|
||||
// Send error
|
||||
res.sendStatus(result);
|
||||
return;
|
||||
}
|
||||
if (typeof result === 'number') {
|
||||
// Send error
|
||||
res.sendStatus(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert JSON(P) response
|
||||
if (result.body === void 0 && result.data !== void 0) {
|
||||
if (typeof result.data === 'object') {
|
||||
result.body = (req.query.pretty === '1' || req.query.pretty === 'true') ? JSON.stringify(result.data, null, 4) : JSON.stringify(result.data);
|
||||
}
|
||||
// Convert JSON(P) response
|
||||
if (result.body === void 0 && result.data !== void 0) {
|
||||
if (typeof result.data === 'object') {
|
||||
result.body =
|
||||
req.query.pretty === '1' || req.query.pretty === 'true'
|
||||
? JSON.stringify(result.data, null, 4)
|
||||
: JSON.stringify(result.data);
|
||||
}
|
||||
|
||||
if (result.js === void 0) {
|
||||
result.js = req.query.callback !== void 0;
|
||||
}
|
||||
if (result.js === void 0) {
|
||||
result.js = req.query.callback !== void 0;
|
||||
}
|
||||
|
||||
if (result.js === true) {
|
||||
let callback;
|
||||
if (result.callback === void 0 && req.query.callback !== void 0) {
|
||||
callback = req.query.callback;
|
||||
if (!callback.match(callbackMatch)) {
|
||||
// Invalid callback
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
callback = result.callback === void 0 ? result.defaultCallback : result.callback;
|
||||
if (callback === void 0) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
}
|
||||
result.body = callback + '(' + result.body + ');';
|
||||
result.type = 'application/javascript; charset=utf-8';
|
||||
} else {
|
||||
result.type = 'application/json; charset=utf-8';
|
||||
}
|
||||
}
|
||||
if (result.js === true) {
|
||||
let callback;
|
||||
if (result.callback === void 0 && req.query.callback !== void 0) {
|
||||
callback = req.query.callback;
|
||||
if (!callback.match(callbackMatch)) {
|
||||
// Invalid callback
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
callback =
|
||||
result.callback === void 0
|
||||
? result.defaultCallback
|
||||
: result.callback;
|
||||
if (callback === void 0) {
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
}
|
||||
result.body = callback + '(' + result.body + ');';
|
||||
result.type = 'application/javascript; charset=utf-8';
|
||||
} else {
|
||||
result.type = 'application/json; charset=utf-8';
|
||||
}
|
||||
}
|
||||
|
||||
// Send cache header
|
||||
if (
|
||||
app.config.cache && app.config.cache.timeout &&
|
||||
(req.get('Pragma') === void 0 || req.get('Pragma').indexOf('no-cache') === -1) &&
|
||||
(req.get('Cache-Control') === void 0 || req.get('Cache-Control').indexOf('no-cache') === -1)
|
||||
) {
|
||||
res.set('Cache-Control', (app.config.cache.private ? 'private' : 'public') + ', max-age=' + app.config.cache.timeout + ', min-refresh=' + app.config.cache['min-refresh']);
|
||||
if (!app.config.cache.private) {
|
||||
res.set('Pragma', 'cache');
|
||||
}
|
||||
}
|
||||
// Send cache header
|
||||
if (
|
||||
app.config.cache &&
|
||||
app.config.cache.timeout &&
|
||||
(req.get('Pragma') === void 0 ||
|
||||
req.get('Pragma').indexOf('no-cache') === -1) &&
|
||||
(req.get('Cache-Control') === void 0 ||
|
||||
req.get('Cache-Control').indexOf('no-cache') === -1)
|
||||
) {
|
||||
res.set(
|
||||
'Cache-Control',
|
||||
(app.config.cache.private ? 'private' : 'public') +
|
||||
', max-age=' +
|
||||
app.config.cache.timeout +
|
||||
', min-refresh=' +
|
||||
app.config.cache['min-refresh']
|
||||
);
|
||||
if (!app.config.cache.private) {
|
||||
res.set('Pragma', 'cache');
|
||||
}
|
||||
}
|
||||
|
||||
// Check for download
|
||||
if (result.filename !== void 0 && (req.query.download === '1' || req.query.download === 'true')) {
|
||||
res.set('Content-Disposition', 'attachment; filename="' + result.filename + '"');
|
||||
}
|
||||
// Check for download
|
||||
if (
|
||||
result.filename !== void 0 &&
|
||||
(req.query.download === '1' || req.query.download === 'true')
|
||||
) {
|
||||
res.set(
|
||||
'Content-Disposition',
|
||||
'attachment; filename="' + result.filename + '"'
|
||||
);
|
||||
}
|
||||
|
||||
// Send data
|
||||
res.type(result.type).send(result.body);
|
||||
// Send data
|
||||
res.type(result.type).send(result.body);
|
||||
};
|
||||
|
|
|
|||
157
src/startup.js
157
src/startup.js
|
|
@ -7,76 +7,103 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
const util = require('util');
|
||||
const promiseEach = require('./promise');
|
||||
|
||||
module.exports = app => new Promise((fulfill, reject) => {
|
||||
let actions = [],
|
||||
logger = app.logger('Starting API...', 60),
|
||||
start = Date.now();
|
||||
module.exports = app =>
|
||||
new Promise((fulfill, reject) => {
|
||||
let actions = [],
|
||||
logger = app.logger('Starting API...', 60),
|
||||
start = Date.now();
|
||||
|
||||
// Check for repositories to synchronize
|
||||
if (app.config.canSync) {
|
||||
switch (app.config['sync-on-startup']) {
|
||||
case 'always':
|
||||
case 'missing':
|
||||
app.dirs.getRepos().forEach(repo => {
|
||||
if (app.sync[repo] && (
|
||||
app.config['sync-on-startup'] === 'always' || !app.dirs.synchronized(repo)
|
||||
)) {
|
||||
actions.push({
|
||||
action: 'sync',
|
||||
repo: repo
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Check for repositories to synchronize
|
||||
if (app.config.canSync) {
|
||||
switch (app.config['sync-on-startup']) {
|
||||
case 'always':
|
||||
case 'missing':
|
||||
app.dirs.getRepos().forEach(repo => {
|
||||
if (
|
||||
app.sync[repo] &&
|
||||
(app.config['sync-on-startup'] === 'always' ||
|
||||
!app.dirs.synchronized(repo))
|
||||
) {
|
||||
actions.push({
|
||||
action: 'sync',
|
||||
repo: repo,
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Load icons
|
||||
actions.push({
|
||||
action: 'load'
|
||||
});
|
||||
// Load icons
|
||||
actions.push({
|
||||
action: 'load',
|
||||
});
|
||||
|
||||
// Parse each promise
|
||||
promiseEach(actions, action => new Promise((fulfill, reject) => {
|
||||
switch (action.action) {
|
||||
case 'load':
|
||||
// Load icons
|
||||
app.reload(null, {
|
||||
logger: logger
|
||||
}).then(() => {
|
||||
if (!Object.keys(app.collections).length) {
|
||||
reject('No collections were found.');
|
||||
} else {
|
||||
fulfill();
|
||||
}
|
||||
}).catch(err => {
|
||||
reject('Error loading collections: ' + util.format(err));
|
||||
});
|
||||
return;
|
||||
// Parse each promise
|
||||
promiseEach(
|
||||
actions,
|
||||
action =>
|
||||
new Promise((fulfill, reject) => {
|
||||
switch (action.action) {
|
||||
case 'load':
|
||||
// Load icons
|
||||
app.reload(null, {
|
||||
logger: logger,
|
||||
})
|
||||
.then(() => {
|
||||
if (!Object.keys(app.collections).length) {
|
||||
reject('No collections were found.');
|
||||
} else {
|
||||
fulfill();
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
reject(
|
||||
'Error loading collections: ' +
|
||||
util.format(err)
|
||||
);
|
||||
});
|
||||
return;
|
||||
|
||||
case 'sync':
|
||||
// Load icons
|
||||
app.sync(action.repo, {
|
||||
noDelay: true,
|
||||
reload: false,
|
||||
logger: logger
|
||||
}).then(res => {
|
||||
fulfill();
|
||||
}).catch(err => {
|
||||
reject('Error synchronizing repository "' + repo + '": ' + util.format(err));
|
||||
});
|
||||
return;
|
||||
}
|
||||
})).then(() => {
|
||||
logger.log('\nStart up process completed in ' + (Date.now() - start) / 1000 + ' seconds.');
|
||||
fulfill();
|
||||
}).catch(err => {
|
||||
logger.error('\nStart up process failed!\n\n' + util.format(err));
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
case 'sync':
|
||||
// Load icons
|
||||
app.sync(action.repo, {
|
||||
noDelay: true,
|
||||
reload: false,
|
||||
logger: logger,
|
||||
})
|
||||
.then(res => {
|
||||
fulfill();
|
||||
})
|
||||
.catch(err => {
|
||||
reject(
|
||||
'Error synchronizing repository "' +
|
||||
repo +
|
||||
'": ' +
|
||||
util.format(err)
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
logger.log(
|
||||
'\nStart up process completed in ' +
|
||||
(Date.now() - start) / 1000 +
|
||||
' seconds.'
|
||||
);
|
||||
fulfill();
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error(
|
||||
'\nStart up process failed!\n\n' + util.format(err)
|
||||
);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
303
src/sync.js
303
src/sync.js
|
|
@ -7,157 +7,210 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const util = require('util');
|
||||
const child_process = require('child_process');
|
||||
|
||||
const defaultOptions = {
|
||||
logger: null,
|
||||
noDelay: false,
|
||||
reload: true
|
||||
logger: null,
|
||||
noDelay: false,
|
||||
reload: true,
|
||||
};
|
||||
|
||||
let active = Object.create(null),
|
||||
queued = Object.create(null);
|
||||
queued = Object.create(null);
|
||||
|
||||
class Sync {
|
||||
constructor(app, repo, options) {
|
||||
this.app = app;
|
||||
this.repo = repo;
|
||||
this.options = options;
|
||||
}
|
||||
constructor(app, repo, options) {
|
||||
this.app = app;
|
||||
this.repo = repo;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
sync() {
|
||||
return new Promise((fulfill, reject) => {
|
||||
this.app.log('Synchronizing repository "' + this.repo + '"...', this.options);
|
||||
sync() {
|
||||
return new Promise((fulfill, reject) => {
|
||||
this.app.log(
|
||||
'Synchronizing repository "' + this.repo + '"...',
|
||||
this.options
|
||||
);
|
||||
|
||||
let time = Date.now(),
|
||||
root = this.app.dirs.storageDir(),
|
||||
targetDir = root + '/' + this.repo + '.' + time,
|
||||
repoURL = this.app.config.sync[this.repo],
|
||||
cmd = this.app.config.sync.git.replace('{target}', '"' + targetDir + '"').replace('{repo}', '"' + repoURL + '"');
|
||||
let time = Date.now(),
|
||||
root = this.app.dirs.storageDir(),
|
||||
targetDir = root + '/' + this.repo + '.' + time,
|
||||
repoURL = this.app.config.sync[this.repo],
|
||||
cmd = this.app.config.sync.git
|
||||
.replace('{target}', '"' + targetDir + '"')
|
||||
.replace('{repo}', '"' + repoURL + '"');
|
||||
|
||||
child_process.exec(cmd, {
|
||||
cwd: root,
|
||||
env: process.env,
|
||||
uid: process.getuid()
|
||||
}, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject('Error executing git:' + util.format(error));
|
||||
return;
|
||||
}
|
||||
child_process.exec(
|
||||
cmd,
|
||||
{
|
||||
cwd: root,
|
||||
env: process.env,
|
||||
uid: process.getuid(),
|
||||
},
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject('Error executing git:' + util.format(error));
|
||||
return;
|
||||
}
|
||||
|
||||
// Done. Set new directory and reload collections
|
||||
this.app.dirs.setSynchronizedRepoDir(this.repo, time, true);
|
||||
// Done. Set new directory and reload collections
|
||||
this.app.dirs.setSynchronizedRepoDir(this.repo, time, true);
|
||||
|
||||
fulfill(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
fulfill(true);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize repository
|
||||
*
|
||||
* @param app
|
||||
* @param repo
|
||||
* @param options
|
||||
* @param fulfill
|
||||
* @param reject
|
||||
*/
|
||||
static sync(app, repo, options, fulfill, reject) {
|
||||
active[repo] = true;
|
||||
queued[repo] = false;
|
||||
/**
|
||||
* Synchronize repository
|
||||
*
|
||||
* @param app
|
||||
* @param repo
|
||||
* @param options
|
||||
* @param fulfill
|
||||
* @param reject
|
||||
*/
|
||||
static sync(app, repo, options, fulfill, reject) {
|
||||
active[repo] = true;
|
||||
queued[repo] = false;
|
||||
|
||||
let sync = new Sync(app, repo, options);
|
||||
sync.sync(fulfill, reject).then(() => {
|
||||
active[repo] = false;
|
||||
if (queued[repo]) {
|
||||
// Retry
|
||||
let retryDelay;
|
||||
try {
|
||||
retryDelay = app.config.sync['repeated-sync-delay'];
|
||||
} catch (err) {
|
||||
retryDelay = 60;
|
||||
}
|
||||
app.log('Repository "' + repo + '" has finished synchronizing, but there is another sync request queued. Will do another sync in ' + retryDelay + ' seconds.', options);
|
||||
let sync = new Sync(app, repo, options);
|
||||
sync.sync(fulfill, reject)
|
||||
.then(() => {
|
||||
active[repo] = false;
|
||||
if (queued[repo]) {
|
||||
// Retry
|
||||
let retryDelay;
|
||||
try {
|
||||
retryDelay = app.config.sync['repeated-sync-delay'];
|
||||
} catch (err) {
|
||||
retryDelay = 60;
|
||||
}
|
||||
app.log(
|
||||
'Repository "' +
|
||||
repo +
|
||||
'" has finished synchronizing, but there is another sync request queued. Will do another sync in ' +
|
||||
retryDelay +
|
||||
' seconds.',
|
||||
options
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
Sync.sync(app, repo, options, fulfill, reject);
|
||||
}, retryDelay * 1000);
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
Sync.sync(app, repo, options, fulfill, reject);
|
||||
}, retryDelay * 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
// Done
|
||||
app.log('Completed synchronization of repository "' + repo + '".', options);
|
||||
if (options.reload && !queued[repo]) {
|
||||
app.reload(repo, options).then(() => {
|
||||
fulfill(true);
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
fulfill(true);
|
||||
}
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
})
|
||||
}
|
||||
// Done
|
||||
app.log(
|
||||
'Completed synchronization of repository "' + repo + '".',
|
||||
options
|
||||
);
|
||||
if (options.reload && !queued[repo]) {
|
||||
app.reload(repo, options)
|
||||
.then(() => {
|
||||
fulfill(true);
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
fulfill(true);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = (app, repo, options) => new Promise((fulfill, reject) => {
|
||||
// Options
|
||||
options = Object.assign(Object.create(null), defaultOptions, typeof options !== 'object' ? options : Object.create(null));
|
||||
module.exports = (app, repo, options) =>
|
||||
new Promise((fulfill, reject) => {
|
||||
// Options
|
||||
options = Object.assign(
|
||||
Object.create(null),
|
||||
defaultOptions,
|
||||
typeof options !== 'object' ? options : Object.create(null)
|
||||
);
|
||||
|
||||
// Check if synchronization is disabled
|
||||
if (!app.config.canSync || !app.config.sync[repo] || !app.config.sync.git) {
|
||||
reject('Synchronization is disabled.');
|
||||
return;
|
||||
}
|
||||
// Check if synchronization is disabled
|
||||
if (
|
||||
!app.config.canSync ||
|
||||
!app.config.sync[repo] ||
|
||||
!app.config.sync.git
|
||||
) {
|
||||
reject('Synchronization is disabled.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if repository sync is already in queue
|
||||
if (queued[repo]) {
|
||||
app.log('Repository "' + repo + '" is already in synchronization queue.', options);
|
||||
fulfill(false);
|
||||
return;
|
||||
}
|
||||
// Check if repository sync is already in queue
|
||||
if (queued[repo]) {
|
||||
app.log(
|
||||
'Repository "' +
|
||||
repo +
|
||||
'" is already in synchronization queue.',
|
||||
options
|
||||
);
|
||||
fulfill(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let delay, retryDelay;
|
||||
try {
|
||||
delay = app.config.sync['sync-delay'];
|
||||
retryDelay = app.config.sync['repeated-sync-delay'];
|
||||
} catch (err) {
|
||||
delay = 60;
|
||||
retryDelay = 60;
|
||||
}
|
||||
if (options.noDelay) {
|
||||
delay = 0;
|
||||
}
|
||||
let delay, retryDelay;
|
||||
try {
|
||||
delay = app.config.sync['sync-delay'];
|
||||
retryDelay = app.config.sync['repeated-sync-delay'];
|
||||
} catch (err) {
|
||||
delay = 60;
|
||||
retryDelay = 60;
|
||||
}
|
||||
if (options.noDelay) {
|
||||
delay = 0;
|
||||
}
|
||||
|
||||
// Add to queue
|
||||
queued[repo] = true;
|
||||
// Add to queue
|
||||
queued[repo] = true;
|
||||
|
||||
// Check if repository is already being synchronized
|
||||
if (active[repo]) {
|
||||
app.log('Repository "' + repo + '" is already being synchronized. Will do another sync ' + retryDelay + ' seconds after previous sync completes.', options);
|
||||
fulfill(false);
|
||||
return;
|
||||
}
|
||||
// Check if repository is already being synchronized
|
||||
if (active[repo]) {
|
||||
app.log(
|
||||
'Repository "' +
|
||||
repo +
|
||||
'" is already being synchronized. Will do another sync ' +
|
||||
retryDelay +
|
||||
' seconds after previous sync completes.',
|
||||
options
|
||||
);
|
||||
fulfill(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create logger if its missing
|
||||
if (!options.logger) {
|
||||
options.logger = app.logger('Synchronizing repository: ' + repo, delay + 15);
|
||||
}
|
||||
|
||||
// Start time
|
||||
if (!delay) {
|
||||
Sync.sync(app, repo, options, fulfill, reject);
|
||||
} else {
|
||||
app.log('Repository "' + repo + '" will start synchronizing in ' + delay + ' seconds.', options);
|
||||
setTimeout(() => {
|
||||
Sync.sync(app, repo, options, fulfill, reject);
|
||||
}, delay * 1000);
|
||||
}
|
||||
});
|
||||
// Create logger if its missing
|
||||
if (!options.logger) {
|
||||
options.logger = app.logger(
|
||||
'Synchronizing repository: ' + repo,
|
||||
delay + 15
|
||||
);
|
||||
}
|
||||
|
||||
// Start time
|
||||
if (!delay) {
|
||||
Sync.sync(app, repo, options, fulfill, reject);
|
||||
} else {
|
||||
app.log(
|
||||
'Repository "' +
|
||||
repo +
|
||||
'" will start synchronizing in ' +
|
||||
delay +
|
||||
' seconds.',
|
||||
options
|
||||
);
|
||||
setTimeout(() => {
|
||||
Sync.sync(app, repo, options, fulfill, reject);
|
||||
}, delay * 1000);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,53 +1,57 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
(() => {
|
||||
const loadJSON = require('../src/json');
|
||||
const loadJSON = require('../src/json');
|
||||
|
||||
const fs = require('fs'),
|
||||
chai = require('chai'),
|
||||
expect = chai.expect,
|
||||
should = chai.should();
|
||||
const fs = require('fs'),
|
||||
chai = require('chai'),
|
||||
expect = chai.expect,
|
||||
should = chai.should();
|
||||
|
||||
describe('Loading JSON file', () => {
|
||||
const filename = __dirname + '/fixtures/test1.json',
|
||||
expectedResult = JSON.parse(fs.readFileSync(filename, 'utf8'));
|
||||
describe('Loading JSON file', () => {
|
||||
const filename = __dirname + '/fixtures/test1.json',
|
||||
expectedResult = JSON.parse(fs.readFileSync(filename, 'utf8'));
|
||||
|
||||
// Check if stream method is available
|
||||
let testStream;
|
||||
try {
|
||||
require('JSONStream');
|
||||
require('event-stream');
|
||||
testStream = true;
|
||||
} catch (err) {
|
||||
testStream = false;
|
||||
}
|
||||
// Check if stream method is available
|
||||
let testStream;
|
||||
try {
|
||||
require('JSONStream');
|
||||
require('event-stream');
|
||||
testStream = true;
|
||||
} catch (err) {
|
||||
testStream = false;
|
||||
}
|
||||
|
||||
// Test with each method
|
||||
['json', 'eval', 'stream'].forEach(method => {
|
||||
it(method, function(done) {
|
||||
if (method === 'stream' && !testStream) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
// Test with each method
|
||||
['json', 'eval', 'stream'].forEach(method => {
|
||||
it(method, function(done) {
|
||||
if (method === 'stream' && !testStream) {
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
// Load file
|
||||
loadJSON(method, filename).then(result => {
|
||||
expect(result.changed).to.be.equal(true);
|
||||
expect(result.data).to.be.eql(expectedResult);
|
||||
// Load file
|
||||
loadJSON(method, filename)
|
||||
.then(result => {
|
||||
expect(result.changed).to.be.equal(true);
|
||||
expect(result.data).to.be.eql(expectedResult);
|
||||
|
||||
// Load file with same hash
|
||||
loadJSON(method, filename, result.hash).then(result2 => {
|
||||
expect(result2.changed).to.be.equal(false);
|
||||
expect(result2.hash).to.be.equal(result.hash);
|
||||
// Load file with same hash
|
||||
loadJSON(method, filename, result.hash)
|
||||
.then(result2 => {
|
||||
expect(result2.changed).to.be.equal(false);
|
||||
expect(result2.hash).to.be.equal(result.hash);
|
||||
|
||||
done();
|
||||
}).catch(err => {
|
||||
done(err);
|
||||
});
|
||||
}).catch(err => {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
done();
|
||||
})
|
||||
.catch(err => {
|
||||
done(err);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,144 +1,154 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
(() => {
|
||||
const log = require('../src/log');
|
||||
const log = require('../src/log');
|
||||
|
||||
const chai = require('chai'),
|
||||
expect = chai.expect,
|
||||
should = chai.should();
|
||||
const chai = require('chai'),
|
||||
expect = chai.expect,
|
||||
should = chai.should();
|
||||
|
||||
describe('Logging messages', () => {
|
||||
it ('logging error to console and mail', done => {
|
||||
let logged = {
|
||||
log: false,
|
||||
mail: false
|
||||
};
|
||||
let fakeApp = {
|
||||
mail: message => {
|
||||
expect(message.indexOf(expectedMessage) !== false).to.be.equal(true);
|
||||
logged.mail = true;
|
||||
},
|
||||
logger: () => {
|
||||
done('logger() should not have been called');
|
||||
},
|
||||
config: {
|
||||
mail: {
|
||||
throttle: 0.2
|
||||
}
|
||||
}
|
||||
};
|
||||
describe('Logging messages', () => {
|
||||
it('logging error to console and mail', done => {
|
||||
let logged = {
|
||||
log: false,
|
||||
mail: false,
|
||||
};
|
||||
let fakeApp = {
|
||||
mail: message => {
|
||||
expect(
|
||||
message.indexOf(expectedMessage) !== false
|
||||
).to.be.equal(true);
|
||||
logged.mail = true;
|
||||
},
|
||||
logger: () => {
|
||||
done('logger() should not have been called');
|
||||
},
|
||||
config: {
|
||||
mail: {
|
||||
throttle: 0.2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let expectedMessage = 'This is a test';
|
||||
log(fakeApp, true, expectedMessage, {
|
||||
console: {
|
||||
error: message => {
|
||||
expect(message.indexOf(expectedMessage) !== false).to.be.equal(true);
|
||||
logged.log = true;
|
||||
},
|
||||
log: message => {
|
||||
done('console.log should not have been called');
|
||||
}
|
||||
}
|
||||
});
|
||||
let expectedMessage = 'This is a test';
|
||||
log(fakeApp, true, expectedMessage, {
|
||||
console: {
|
||||
error: message => {
|
||||
expect(
|
||||
message.indexOf(expectedMessage) !== false
|
||||
).to.be.equal(true);
|
||||
logged.log = true;
|
||||
},
|
||||
log: message => {
|
||||
done('console.log should not have been called');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(logged).to.be.eql({
|
||||
log: true,
|
||||
mail: true
|
||||
});
|
||||
done();
|
||||
}, 500);
|
||||
});
|
||||
setTimeout(() => {
|
||||
expect(logged).to.be.eql({
|
||||
log: true,
|
||||
mail: true,
|
||||
});
|
||||
done();
|
||||
}, 500);
|
||||
});
|
||||
|
||||
it ('logging message to console', done => {
|
||||
let logged = {
|
||||
log: false
|
||||
};
|
||||
let fakeApp = {
|
||||
mail: message => {
|
||||
done('mail() should not have been called');
|
||||
},
|
||||
logger: () => {
|
||||
done('logger() should not have been called');
|
||||
},
|
||||
config: {
|
||||
mail: {
|
||||
throttle: 0.2
|
||||
}
|
||||
}
|
||||
};
|
||||
it('logging message to console', done => {
|
||||
let logged = {
|
||||
log: false,
|
||||
};
|
||||
let fakeApp = {
|
||||
mail: message => {
|
||||
done('mail() should not have been called');
|
||||
},
|
||||
logger: () => {
|
||||
done('logger() should not have been called');
|
||||
},
|
||||
config: {
|
||||
mail: {
|
||||
throttle: 0.2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let expectedMessage = 'This is a test';
|
||||
log(fakeApp, false, expectedMessage, {
|
||||
console: {
|
||||
log: message => {
|
||||
expect(message.indexOf(expectedMessage) !== false).to.be.equal(true);
|
||||
logged.log = true;
|
||||
},
|
||||
error: message => {
|
||||
done('console.log should not have been called');
|
||||
}
|
||||
}
|
||||
});
|
||||
let expectedMessage = 'This is a test';
|
||||
log(fakeApp, false, expectedMessage, {
|
||||
console: {
|
||||
log: message => {
|
||||
expect(
|
||||
message.indexOf(expectedMessage) !== false
|
||||
).to.be.equal(true);
|
||||
logged.log = true;
|
||||
},
|
||||
error: message => {
|
||||
done('console.log should not have been called');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(logged).to.be.eql({
|
||||
log: true
|
||||
});
|
||||
done();
|
||||
}, 500);
|
||||
});
|
||||
setTimeout(() => {
|
||||
expect(logged).to.be.eql({
|
||||
log: true,
|
||||
});
|
||||
done();
|
||||
}, 500);
|
||||
});
|
||||
|
||||
it ('logging same error only once', done => {
|
||||
let logged = {
|
||||
log: false,
|
||||
mail: false
|
||||
};
|
||||
let fakeApp = {
|
||||
mail: message => {
|
||||
if (logged.mail) {
|
||||
done('mail() was called twice');
|
||||
}
|
||||
expect(message.indexOf(expectedMessage) !== false).to.be.equal(true);
|
||||
logged.mail = true;
|
||||
},
|
||||
logger: () => {
|
||||
done('logger() should not have been called');
|
||||
},
|
||||
config: {
|
||||
mail: {
|
||||
throttle: 0.2
|
||||
}
|
||||
}
|
||||
};
|
||||
it('logging same error only once', done => {
|
||||
let logged = {
|
||||
log: false,
|
||||
mail: false,
|
||||
};
|
||||
let fakeApp = {
|
||||
mail: message => {
|
||||
if (logged.mail) {
|
||||
done('mail() was called twice');
|
||||
}
|
||||
expect(
|
||||
message.indexOf(expectedMessage) !== false
|
||||
).to.be.equal(true);
|
||||
logged.mail = true;
|
||||
},
|
||||
logger: () => {
|
||||
done('logger() should not have been called');
|
||||
},
|
||||
config: {
|
||||
mail: {
|
||||
throttle: 0.2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let expectedMessage = 'This is a test',
|
||||
fakeConsole = {
|
||||
error: message => {
|
||||
expect(message.indexOf(expectedMessage) !== false).to.be.equal(true);
|
||||
logged.log = true;
|
||||
},
|
||||
log: message => {
|
||||
done('console.log should not have been called');
|
||||
}
|
||||
};
|
||||
let expectedMessage = 'This is a test',
|
||||
fakeConsole = {
|
||||
error: message => {
|
||||
expect(
|
||||
message.indexOf(expectedMessage) !== false
|
||||
).to.be.equal(true);
|
||||
logged.log = true;
|
||||
},
|
||||
log: message => {
|
||||
done('console.log should not have been called');
|
||||
},
|
||||
};
|
||||
|
||||
log(fakeApp, true, expectedMessage, {
|
||||
console: fakeConsole,
|
||||
key: 'test'
|
||||
});
|
||||
log(fakeApp, true, expectedMessage, {
|
||||
console: fakeConsole,
|
||||
key: 'test'
|
||||
});
|
||||
log(fakeApp, true, expectedMessage, {
|
||||
console: fakeConsole,
|
||||
key: 'test',
|
||||
});
|
||||
log(fakeApp, true, expectedMessage, {
|
||||
console: fakeConsole,
|
||||
key: 'test',
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(logged).to.be.eql({
|
||||
log: true,
|
||||
mail: true
|
||||
});
|
||||
done();
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
setTimeout(() => {
|
||||
expect(logged).to.be.eql({
|
||||
log: true,
|
||||
mail: true,
|
||||
});
|
||||
done();
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,107 +1,122 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
(() => {
|
||||
const chai = require('chai'),
|
||||
expect = chai.expect,
|
||||
should = chai.should();
|
||||
const chai = require('chai'),
|
||||
expect = chai.expect,
|
||||
should = chai.should();
|
||||
|
||||
describe('Splitting query string', () => {
|
||||
it('3 part requests', () => {
|
||||
const exp = /^\/([a-z0-9-]+)\/([a-z0-9-]+)\.(js|json|svg)$/;
|
||||
describe('Splitting query string', () => {
|
||||
it('3 part requests', () => {
|
||||
const exp = /^\/([a-z0-9-]+)\/([a-z0-9-]+)\.(js|json|svg)$/;
|
||||
|
||||
function test(str) {
|
||||
let result = str.match(exp);
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
function test(str) {
|
||||
let result = str.match(exp);
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove first parameter and named parameters that don't exist in Expression.js params
|
||||
result.shift();
|
||||
delete result.index;
|
||||
delete result.input;
|
||||
// Remove first parameter and named parameters that don't exist in Expression.js params
|
||||
result.shift();
|
||||
delete result.index;
|
||||
delete result.input;
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// SVG
|
||||
expect(test('/foo/bar.svg')).to.be.eql(['foo', 'bar', 'svg']);
|
||||
expect(test('/fa-pro/test-icon.svg')).to.be.eql(['fa-pro', 'test-icon', 'svg']);
|
||||
// SVG
|
||||
expect(test('/foo/bar.svg')).to.be.eql(['foo', 'bar', 'svg']);
|
||||
expect(test('/fa-pro/test-icon.svg')).to.be.eql([
|
||||
'fa-pro',
|
||||
'test-icon',
|
||||
'svg',
|
||||
]);
|
||||
|
||||
// icons
|
||||
expect(test('/foo/icons.js')).to.be.eql(['foo', 'icons', 'js']);
|
||||
expect(test('/long-prefixed-v1/icons.json')).to.be.eql(['long-prefixed-v1', 'icons', 'json']);
|
||||
// icons
|
||||
expect(test('/foo/icons.js')).to.be.eql(['foo', 'icons', 'js']);
|
||||
expect(test('/long-prefixed-v1/icons.json')).to.be.eql([
|
||||
'long-prefixed-v1',
|
||||
'icons',
|
||||
'json',
|
||||
]);
|
||||
|
||||
// Too long
|
||||
expect(test('/fa-pro/test/icon.svg')).to.be.equal(null);
|
||||
// Too long
|
||||
expect(test('/fa-pro/test/icon.svg')).to.be.equal(null);
|
||||
|
||||
// Upper case
|
||||
expect(test('/SomePrefix/Test.SVG')).to.be.equal(null);
|
||||
// Upper case
|
||||
expect(test('/SomePrefix/Test.SVG')).to.be.equal(null);
|
||||
|
||||
// Invalid characters
|
||||
expect(test('/foo_bar/test.svg')).to.be.equal(null);
|
||||
});
|
||||
// Invalid characters
|
||||
expect(test('/foo_bar/test.svg')).to.be.equal(null);
|
||||
});
|
||||
|
||||
it('2 part js/json requests', () => {
|
||||
const exp = /^\/([a-z0-9-]+)\.(js|json)$/;
|
||||
it('2 part js/json requests', () => {
|
||||
const exp = /^\/([a-z0-9-]+)\.(js|json)$/;
|
||||
|
||||
function test(str) {
|
||||
let result = str.match(exp);
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
function test(str) {
|
||||
let result = str.match(exp);
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove first parameter and named parameters that don't exist in Expression.js params
|
||||
result.shift();
|
||||
delete result.index;
|
||||
delete result.input;
|
||||
// Remove first parameter and named parameters that don't exist in Expression.js params
|
||||
result.shift();
|
||||
delete result.index;
|
||||
delete result.input;
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// icons
|
||||
expect(test('/foo.js')).to.be.eql(['foo', 'js']);
|
||||
expect(test('/long-prefixed-v1.json')).to.be.eql(['long-prefixed-v1', 'json']);
|
||||
// icons
|
||||
expect(test('/foo.js')).to.be.eql(['foo', 'js']);
|
||||
expect(test('/long-prefixed-v1.json')).to.be.eql([
|
||||
'long-prefixed-v1',
|
||||
'json',
|
||||
]);
|
||||
|
||||
// Too long
|
||||
expect(test('/fa-pro/icons.js')).to.be.equal(null);
|
||||
// Too long
|
||||
expect(test('/fa-pro/icons.js')).to.be.equal(null);
|
||||
|
||||
// Upper case
|
||||
expect(test('/SomePrefix.JSON')).to.be.equal(null);
|
||||
// Upper case
|
||||
expect(test('/SomePrefix.JSON')).to.be.equal(null);
|
||||
|
||||
// Invalid characters
|
||||
expect(test('/foo_bar.json')).to.be.equal(null);
|
||||
});
|
||||
// Invalid characters
|
||||
expect(test('/foo_bar.json')).to.be.equal(null);
|
||||
});
|
||||
|
||||
it('2 part svg requests', () => {
|
||||
const exp = /^\/([a-z0-9:\-]+)\.svg$/;
|
||||
it('2 part svg requests', () => {
|
||||
const exp = /^\/([a-z0-9:\-]+)\.svg$/;
|
||||
|
||||
function test(str) {
|
||||
let result = str.match(exp);
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
function test(str) {
|
||||
let result = str.match(exp);
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove first parameter and named parameters that don't exist in Expression.js params
|
||||
result.shift();
|
||||
delete result.index;
|
||||
delete result.input;
|
||||
// Remove first parameter and named parameters that don't exist in Expression.js params
|
||||
result.shift();
|
||||
delete result.index;
|
||||
delete result.input;
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// icons
|
||||
expect(test('/foo.svg')).to.be.eql(['foo']);
|
||||
expect(test('/long-prefixed-v1.svg')).to.be.eql(['long-prefixed-v1']);
|
||||
expect(test('/long-prefixed:icon-v1.svg')).to.be.eql(['long-prefixed:icon-v1']);
|
||||
// icons
|
||||
expect(test('/foo.svg')).to.be.eql(['foo']);
|
||||
expect(test('/long-prefixed-v1.svg')).to.be.eql([
|
||||
'long-prefixed-v1',
|
||||
]);
|
||||
expect(test('/long-prefixed:icon-v1.svg')).to.be.eql([
|
||||
'long-prefixed:icon-v1',
|
||||
]);
|
||||
|
||||
// Too long
|
||||
expect(test('/fa-pro/icons.svg')).to.be.equal(null);
|
||||
// Too long
|
||||
expect(test('/fa-pro/icons.svg')).to.be.equal(null);
|
||||
|
||||
// Upper case
|
||||
expect(test('/SomePrefix.SVG')).to.be.equal(null);
|
||||
// Upper case
|
||||
expect(test('/SomePrefix.SVG')).to.be.equal(null);
|
||||
|
||||
// Invalid characters
|
||||
expect(test('/foo_bar.svg')).to.be.equal(null);
|
||||
});
|
||||
});
|
||||
// Invalid characters
|
||||
expect(test('/foo_bar.svg')).to.be.equal(null);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,167 +1,189 @@
|
|||
"use strict";
|
||||
'use strict';
|
||||
|
||||
(() => {
|
||||
const chai = require('chai'),
|
||||
expect = chai.expect,
|
||||
should = chai.should();
|
||||
const chai = require('chai'),
|
||||
expect = chai.expect,
|
||||
should = chai.should();
|
||||
|
||||
const Collection = require('@iconify/json-tools').Collection,
|
||||
request = require('../src/request-icons');
|
||||
const Collection = require('@iconify/json-tools').Collection,
|
||||
request = require('../src/request-icons');
|
||||
|
||||
let collection1 = new Collection('test'),
|
||||
collection2 = new Collection('test2');
|
||||
let collection1 = new Collection('test'),
|
||||
collection2 = new Collection('test2');
|
||||
|
||||
const parseQuery = (prefix, query, ext, params) => {
|
||||
let result = null;
|
||||
request({
|
||||
// fake app
|
||||
response: (req, res, data) => {
|
||||
result = data;
|
||||
},
|
||||
collections: {
|
||||
test1: collection1,
|
||||
test2: collection2
|
||||
}
|
||||
}, {
|
||||
// fake request
|
||||
query: params
|
||||
}, {}, prefix, query, ext);
|
||||
return result;
|
||||
};
|
||||
const parseQuery = (prefix, query, ext, params) => {
|
||||
let result = null;
|
||||
request(
|
||||
{
|
||||
// fake app
|
||||
response: (req, res, data) => {
|
||||
result = data;
|
||||
},
|
||||
collections: {
|
||||
test1: collection1,
|
||||
test2: collection2,
|
||||
},
|
||||
},
|
||||
{
|
||||
// fake request
|
||||
query: params,
|
||||
},
|
||||
{},
|
||||
prefix,
|
||||
query,
|
||||
ext
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
describe('Requests for icons and collections', () => {
|
||||
before(() => {
|
||||
collection1.loadJSON({
|
||||
prefix: 'test',
|
||||
icons: {
|
||||
icon1: {
|
||||
body: '<icon1 fill="currentColor" />',
|
||||
width: 30
|
||||
},
|
||||
icon2: {
|
||||
body: '<icon2 />'
|
||||
}
|
||||
},
|
||||
aliases: {
|
||||
alias1: {
|
||||
parent: 'icon2',
|
||||
hFlip: true
|
||||
}
|
||||
},
|
||||
width: 24,
|
||||
height: 24
|
||||
});
|
||||
describe('Requests for icons and collections', () => {
|
||||
before(() => {
|
||||
collection1.loadJSON({
|
||||
prefix: 'test',
|
||||
icons: {
|
||||
icon1: {
|
||||
body: '<icon1 fill="currentColor" />',
|
||||
width: 30,
|
||||
},
|
||||
icon2: {
|
||||
body: '<icon2 />',
|
||||
},
|
||||
},
|
||||
aliases: {
|
||||
alias1: {
|
||||
parent: 'icon2',
|
||||
hFlip: true,
|
||||
},
|
||||
},
|
||||
width: 24,
|
||||
height: 24,
|
||||
});
|
||||
|
||||
collection2.loadJSON({
|
||||
icons: {
|
||||
'test2-icon1': {
|
||||
body: '<icon1 fill="currentColor" />',
|
||||
width: 30
|
||||
},
|
||||
'test2-icon2': {
|
||||
body: '<icon2 />'
|
||||
},
|
||||
'test2-icon3': {
|
||||
body: '<defs><foo id="bar" /></defs><bar use="url(#bar)" fill="currentColor" stroke="currentColor" />'
|
||||
}
|
||||
},
|
||||
aliases: {
|
||||
'test2-alias1': {
|
||||
parent: 'test2-icon2',
|
||||
hFlip: true
|
||||
}
|
||||
},
|
||||
width: 24,
|
||||
height: 24
|
||||
});
|
||||
collection2.loadJSON({
|
||||
icons: {
|
||||
'test2-icon1': {
|
||||
body: '<icon1 fill="currentColor" />',
|
||||
width: 30,
|
||||
},
|
||||
'test2-icon2': {
|
||||
body: '<icon2 />',
|
||||
},
|
||||
'test2-icon3': {
|
||||
body:
|
||||
'<defs><foo id="bar" /></defs><bar use="url(#bar)" fill="currentColor" stroke="currentColor" />',
|
||||
},
|
||||
},
|
||||
aliases: {
|
||||
'test2-alias1': {
|
||||
parent: 'test2-icon2',
|
||||
hFlip: true,
|
||||
},
|
||||
},
|
||||
width: 24,
|
||||
height: 24,
|
||||
});
|
||||
|
||||
expect(collection1.items).to.not.be.equal(null);
|
||||
expect(collection2.items).to.not.be.equal(null);
|
||||
});
|
||||
expect(collection1.items).to.not.be.equal(null);
|
||||
expect(collection2.items).to.not.be.equal(null);
|
||||
});
|
||||
|
||||
it('icons list', () => {
|
||||
// Simple query with prefix
|
||||
expect(parseQuery('test1', 'icons', 'js', {
|
||||
icons: 'alias1'
|
||||
})).to.be.eql({
|
||||
js: true,
|
||||
defaultCallback: 'SimpleSVG._loaderCallback',
|
||||
data: {
|
||||
prefix: 'test',
|
||||
icons: {
|
||||
icon2: { body: '<icon2 />' }
|
||||
},
|
||||
aliases: {
|
||||
alias1: { parent: 'icon2', hFlip: true }
|
||||
},
|
||||
width: 24,
|
||||
height: 24
|
||||
}
|
||||
});
|
||||
it('icons list', () => {
|
||||
// Simple query with prefix
|
||||
expect(
|
||||
parseQuery('test1', 'icons', 'js', {
|
||||
icons: 'alias1',
|
||||
})
|
||||
).to.be.eql({
|
||||
js: true,
|
||||
defaultCallback: 'SimpleSVG._loaderCallback',
|
||||
data: {
|
||||
prefix: 'test',
|
||||
icons: {
|
||||
icon2: { body: '<icon2 />' },
|
||||
},
|
||||
aliases: {
|
||||
alias1: { parent: 'icon2', hFlip: true },
|
||||
},
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
});
|
||||
|
||||
// Query collection without prefix, json
|
||||
expect(parseQuery('test2', 'icons', 'json', {
|
||||
icons: 'alias1'
|
||||
})).to.be.eql({
|
||||
js: false,
|
||||
defaultCallback: 'SimpleSVG._loaderCallback',
|
||||
data: {
|
||||
prefix: 'test2',
|
||||
icons: {
|
||||
icon2: { body: '<icon2 />' }
|
||||
},
|
||||
aliases: {
|
||||
alias1: { parent: 'icon2', hFlip: true }
|
||||
},
|
||||
width: 24,
|
||||
height: 24
|
||||
}
|
||||
});
|
||||
// Query collection without prefix, json
|
||||
expect(
|
||||
parseQuery('test2', 'icons', 'json', {
|
||||
icons: 'alias1',
|
||||
})
|
||||
).to.be.eql({
|
||||
js: false,
|
||||
defaultCallback: 'SimpleSVG._loaderCallback',
|
||||
data: {
|
||||
prefix: 'test2',
|
||||
icons: {
|
||||
icon2: { body: '<icon2 />' },
|
||||
},
|
||||
aliases: {
|
||||
alias1: { parent: 'icon2', hFlip: true },
|
||||
},
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
});
|
||||
|
||||
// Custom callback
|
||||
expect(parseQuery('test1', 'icons', 'js', {
|
||||
icons: 'icon1,icon2',
|
||||
callback: 'console.log'
|
||||
})).to.be.eql({
|
||||
js: true,
|
||||
defaultCallback: 'SimpleSVG._loaderCallback',
|
||||
data: {
|
||||
prefix: 'test',
|
||||
icons: {
|
||||
icon1: { body: '<icon1 fill="currentColor" />', width: 30 },
|
||||
icon2: { body: '<icon2 />' }
|
||||
},
|
||||
width: 24,
|
||||
height: 24
|
||||
}
|
||||
});
|
||||
});
|
||||
// Custom callback
|
||||
expect(
|
||||
parseQuery('test1', 'icons', 'js', {
|
||||
icons: 'icon1,icon2',
|
||||
callback: 'console.log',
|
||||
})
|
||||
).to.be.eql({
|
||||
js: true,
|
||||
defaultCallback: 'SimpleSVG._loaderCallback',
|
||||
data: {
|
||||
prefix: 'test',
|
||||
icons: {
|
||||
icon1: {
|
||||
body: '<icon1 fill="currentColor" />',
|
||||
width: 30,
|
||||
},
|
||||
icon2: { body: '<icon2 />' },
|
||||
},
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('svg', () => {
|
||||
// Simple icon
|
||||
expect(parseQuery('test1', 'icon1', 'svg', {
|
||||
})).to.be.eql({
|
||||
filename: 'icon1.svg',
|
||||
type: 'image/svg+xml; charset=utf-8',
|
||||
body: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1.25em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 30 24" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><icon1 fill="currentColor" /></svg>'
|
||||
});
|
||||
it('svg', () => {
|
||||
// Simple icon
|
||||
expect(parseQuery('test1', 'icon1', 'svg', {})).to.be.eql({
|
||||
filename: 'icon1.svg',
|
||||
type: 'image/svg+xml; charset=utf-8',
|
||||
body:
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1.25em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 30 24" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><icon1 fill="currentColor" /></svg>',
|
||||
});
|
||||
|
||||
// Icon with custom attributes
|
||||
expect(parseQuery('test2', 'alias1', 'svg', {
|
||||
color: 'red'
|
||||
})).to.be.eql({
|
||||
filename: 'alias1.svg',
|
||||
type: 'image/svg+xml; charset=utf-8',
|
||||
body: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><g transform="translate(24 0) scale(-1 1)"><icon2 /></g></svg>'
|
||||
});
|
||||
// Icon with custom attributes
|
||||
expect(
|
||||
parseQuery('test2', 'alias1', 'svg', {
|
||||
color: 'red',
|
||||
})
|
||||
).to.be.eql({
|
||||
filename: 'alias1.svg',
|
||||
type: 'image/svg+xml; charset=utf-8',
|
||||
body:
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><g transform="translate(24 0) scale(-1 1)"><icon2 /></g></svg>',
|
||||
});
|
||||
|
||||
// Icon with id replacement
|
||||
let result = parseQuery('test2', 'icon3', 'svg', {
|
||||
color: 'red',
|
||||
rotate: '90deg'
|
||||
}).body.replace(/IconifyId-[0-9a-f]+-[0-9a-f]+-[0-9]+/g, 'some-id');
|
||||
// Icon with id replacement
|
||||
let result = parseQuery('test2', 'icon3', 'svg', {
|
||||
color: 'red',
|
||||
rotate: '90deg',
|
||||
}).body.replace(/IconifyId-[0-9a-f]+-[0-9a-f]+-[0-9]+/g, 'some-id');
|
||||
|
||||
expect(result).to.be.equal('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><g transform="rotate(90 12 12)"><defs><foo id="some-id" /></defs><bar use="url(#some-id)" fill="red" stroke="red" /></g></svg>');
|
||||
});
|
||||
});
|
||||
expect(result).to.be.equal(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><g transform="rotate(90 12 12)"><defs><foo id="some-id" /></defs><bar use="url(#some-id)" fill="red" stroke="red" /></g></svg>'
|
||||
);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
Loading…
Reference in New Issue