Files
yunkong2.js-controller/lib/objects/objectsInMemServer.js
2018-09-17 20:32:19 +08:00

3170 lines
122 KiB
JavaScript

/**
* Object DB in memory - Server
*
* Copyright 2013-2018 bluefox <dogafox@gmail.com>
*
* MIT License
*
*/
/* jshint -W097 */
/* jshint strict: false */
/* jslint node: true */
/* jshint -W061 */
'use strict';
const extend = require('node.extend');
const fs = require('fs');
const socketio = require('socket.io');
const tools = require(__dirname + '/../tools');
const getDefaultDataDir = tools.getDefaultDataDir;
const stream = require('stream');
const util = require('util');
const Writable = stream.Writable;
let memStore = {};
/* Writable memory stream */
function WMStrm(key, options) {
// allow use without new operator
if (!(this instanceof WMStrm)) return new WMStrm(key, options);
Writable.call(this, options); // init super
this.key = key; // save key
memStore[key] = new Buffer(''); // empty
}
util.inherits(WMStrm, Writable);
WMStrm.prototype._write = function (chunk, enc, cb) {
if (chunk) {
// our memory store stores things in buffers
let buffer = (Buffer.isBuffer(chunk)) ?
chunk : // already is Buffer use it
new Buffer(chunk, enc); // string, convert
// concatenate to the buffer already there
if (!memStore[this.key]) {
memStore[this.key] = new Buffer('');
console.log('memstore for ' + this.key + ' is null');
}
memStore[this.key] = Buffer.concat([memStore[this.key], buffer]);
}
if (!cb) throw 'Callback is empty';
cb();
};
function ObjectsInMemServer(settings) {
if (!(this instanceof ObjectsInMemServer)) return new ObjectsInMemServer(settings);
settings = settings || {};
let change;
let zlib;
let that = this;
let objects = {};
let fileOptions = {};
let files = {};
let configTimer = null;
let writeTimer = null;
let writeIds = [];
let users = {};
let groups = {};
let preserveSettings = [];
let regUser = /^system\.user/;
let regGroup = /^system\.group/;
let defaultAcl = {
groups: [],
acl: {
file: {
list: false,
read: false,
write: false,
create: false,
'delete': false
},
object: {
list: false,
read: false,
write: false,
'delete': false
}
}
};
let defaultNewAcl = settings.defaultNewAcl || null;
let namespace = settings.namespace || settings.hostname || '';
let lastSave = null;
let dataDir = (settings.connection.dataDir || getDefaultDataDir());
if (dataDir) {
if (dataDir[0] === '.' && dataDir[1] === '.') {
dataDir = __dirname + '/../../' + dataDir;
} else if (dataDir[0] === '.' && dataDir[1] === '/') {
dataDir = __dirname + '/../../' + dataDir.substring(2);
}
}
dataDir = dataDir.replace(/\\/g, '/');
if (dataDir[dataDir.length - 1] !== '/') dataDir += '/';
// Create data directory
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir);
}
let objectsName = dataDir + 'objects.json';
const objectsDir = dataDir + 'files/';
settings.backup = settings.backup || {
disabled: false, // deactivates
files: 24, // minimum number of files
hours: 48, // hours
period: 120, // minutes
path: '' // absolute path
};
const backupDir = settings.backup.path || (dataDir + 'backup-objects/');
if (!settings.backup.disabled) {
zlib = zlib || require('zlib');
// Interval in minutes => to milliseconds
settings.backup.period = settings.backup.period === undefined ? 120 : parseInt(settings.backup.period);
if (isNaN(settings.backup.period)) {
settings.backup.period = 120;
}
settings.backup.period *= 60000;
settings.backup.files = settings.backup.files === undefined ? 24 : parseInt(settings.backup.files);
if (isNaN(settings.backup.files)) {
settings.backup.files = 24;
}
settings.backup.hours = settings.backup.hours === undefined ? 48 : parseInt(settings.backup.hours);
if (isNaN(settings.backup.hours)) {
settings.backup.hours = 48;
}
// Create backup directory
if (!fs.existsSync(backupDir)) {
fs.mkdirSync(backupDir);
}
}
let log = settings.logger;
if (!log) {
log = {
silly: function (msg) {/*console.log(msg);*/},
debug: function (msg) {/*console.log(msg);*/},
info: function (msg) {/*console.log(msg);*/},
warn: function (msg) {
console.log(msg);
},
error: function (msg) {
console.log(msg);
}
};
} else if (!log.silly) {
log.silly = log.debug;
}
let server = {
app: null,
server: null,
io: null,
settings: settings
};
/*function prepareRights(options) {
let fOptions = {};
options = options || {};
if (!options.user) {
options = {
user: 'system.user.admin',
params: options
};
}
// acl.owner = user that creates or owns the file
// acl.group = group, that assigned to file
// acl.permissions = '0777' - default 1 (execute, 2 write, 4 read
if (!options.user) {
fOptions.acl = {
owner: 'system.user.admin',
ownerGroup: 'system.group.administrator',
permissions: 0x644 // '0777'
};
} else {
fOptions.acl = {
owner: options.user
};
fOptions.acl.ownerGroup = options.group;
fOptions.acl.permissions = 0x644;
}
fOptions.acl.ownerGroup = fOptions.acl.ownerGroup || 'system.group.administrator';
return fOptions;
}*/
// -------------- FILE FUNCTIONS -------------------------------------------
// memServer specific function
function mkpathSync(rootpath, dirpath) {
// Remove filename
dirpath = dirpath.split('/');
dirpath.pop();
if (!dirpath.length) return;
for (let i = 0; i < dirpath.length; i++) {
rootpath += dirpath[i] + '/';
if (!fs.existsSync(rootpath)) {
fs.mkdirSync(rootpath);
}
}
}
function saveFileSettings(id, force) {
if (typeof id === 'boolean') {
force = id;
id = undefined;
}
if (id !== undefined && writeIds.indexOf(id) === -1) writeIds.push(id);
if (writeTimer) clearTimeout(writeTimer);
// if store immediately
if (force) {
writeTimer = null;
// Store dirs description
for (let _id = 0; _id < writeIds.length; _id++) {
try {
fs.writeFileSync(objectsDir + writeIds[_id] + '/_data.json', JSON.stringify(fileOptions[writeIds[_id]]));
} catch (e) {
log.error(namespace + ' Cannot write files: ' + objectsDir + writeIds[_id] + '/_data.json: ' + e.message);
}
}
writeIds = [];
} else {
writeTimer = setTimeout(function () {
// Store dirs description
for (let id = 0; id < writeIds.length; id++) {
try {
fs.writeFileSync(objectsDir + writeIds[id] + '/_data.json', JSON.stringify(fileOptions[writeIds[id]]));
} catch (e) {
log.error(namespace + ' Cannot write files: ' + objectsDir + writeIds[id] + '/_data.json: ' + e.message);
}
}
writeIds = [];
}, 1000);
}
}
function checkFile(id, name, options, flag) {
if (typeof fileOptions[id][name].acl !== 'object') {
fileOptions[id][name] = {
mimeType: fileOptions[id][name],
acl: {
owner: (defaultNewAcl && defaultNewAcl.owner) || 'system.user.admin',
ownerGroup: (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator',
permissions: (defaultNewAcl && defaultNewAcl.file) || 0x644 // '0644'
}
};
}
// Set default owner group
fileOptions[id][name].acl.ownerGroup = fileOptions[id][name].acl.ownerGroup || (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator';
fileOptions[id][name].acl.owner = fileOptions[id][name].acl.owner || (defaultNewAcl && defaultNewAcl.owner) || 'system.user.admin';
fileOptions[id][name].acl.permissions = fileOptions[id][name].acl.permissions || (defaultNewAcl && defaultNewAcl.file) || 0x644; // '0644'
if (options.user !== 'system.user.admin' &&
options.groups.indexOf('system.group.administrator') === -1 &&
fileOptions[id][name].acl) {
if (fileOptions[id][name].acl.owner !== options.user) {
// Check if the user is in the group
if (options.groups.indexOf(fileOptions[id][name].acl.ownerGroup) !== -1) {
// Check group rights
if (!(fileOptions[id][name].acl.permissions & (flag << 4))) {
return false;
}
} else {
// everybody
if (!(fileOptions[id][name].acl.permissions & flag)) {
return false;
}
}
} else {
// Check user rights
if (!(fileOptions[id][name].acl.permissions & (flag << 8))) {
return false;
}
}
}
return true;
}
function checkFileRights(id, name, options, flag, callback) {
options = options || {};
if (!options.user) {
// Before files converted, lets think: if no options it is admin
options = {
user: 'system.user.admin',
params: options,
group: 'system.group.administrator'
};
}
if (options.checked) {
return callback(null, options);
}
if (!options.acl) {
that.getUserGroup(options.user, function (user, groups, acl) {
options.acl = acl || {};
options.groups = groups;
options.group = groups ? groups[0] : null;
checkFileRights(id, name, options, flag, callback);
});
return;
}
// If user may write
if (flag === 2 && !options.acl.file.write) {// write
return callback('permissionError', options);
}
// If user may read
if (flag === 4 && !options.acl.file.read) {// read
return callback('permissionError', options);
}
// read rights of file
if (!fileOptions[id]) {
if (fs.existsSync(objectsDir + id + '/_data.json')) {
try {
fileOptions[id] = JSON.parse(fs.readFileSync(objectsDir + id + '/_data.json', 'utf8'));
} catch (e) {
log.error(namespace + ' Cannot parse ' + objectsDir + id + '/_data.json: ' + e);
}
} else {
fileOptions[id] = {};
}
}
options.checked = true;
if (!name || !fileOptions[id] || !fileOptions[id][name]) {
return callback(null, options);
}
if (checkFile(id, name, options,flag)) {
return callback(null, options);
} else {
return callback('permissionError', options);
}
/*if (typeof fileOptions[id][name].acl !== 'object') {
fileOptions[id][name] = {
mimeType: fileOptions[id][name],
acl: {
owner: 'system.user.admin',
permissions: 0x644,
ownerGroup: 'system.group.administrator'
}
};
}
// Set default onwer group
fileOptions[id][name].acl.ownerGroup = fileOptions[id][name].acl.ownerGroup || 'system.group.administrator';
if (options.user !== 'system.user.admin' &&
options.groups.indexOf('system.group.administrator') === -1 &&
fileOptions[id][name].acl) {
if (fileOptions[id][name].acl.owner !== options.user) {
// Check if the user is in the group
if (options.groups.indexOf(fileOptions[id][name].acl.ownerGroup) !== -1) {
// Check group rights
if (!(fileOptions[id][name].acl.permissions & (flag << 4))) {
return callback('permissionError', options);
}
} else {
// everybody
if (!(fileOptions[id][name].acl.permissions & flag)) {
return callback('permissionError', options);
}
}
} else {
// Check user rights
if (!(fileOptions[id][name].acl.permissions & (flag << 8))) {
return callback('permissionError', options);
}
}
}
return callback(null, options);*/
}
function setDefaultAcl(callback) {
try {
defaultNewAcl = Object.assign({}, objects['system.config'].common.defaultNewAcl);
} catch (e) {
defaultNewAcl = {
owner: 'system.user.admin',
ownerGroup: 'system.group.administrator',
object: 0x664,
state: 0x664,
file: 0x664
};
objects['system.config'].common.defaultNewAcl = Object.assign({}, defaultNewAcl);
}
let count = 0;
// Set all objects without ACL to this one
for (let id in objects) {
if (objects.hasOwnProperty(id) && objects[id] && !objects[id].acl) {
objects[id].acl = Object.assign({}, defaultNewAcl);
delete objects[id].acl.file;
if (objects[id].type !== 'state') {
delete objects[id].acl.state;
}
count++;
}
}
if (typeof callback === 'function') callback(null, count);
}
this.getUserGroup = function (user, callback) {
if (!user || typeof user !== 'string' || !user.match(/^system\.user\./)) {
console.log('invalid user name: ' + user);
user = JSON.stringify(user);
return callback.call(that, user, [], Object.assign({}, defaultAcl.acl));
}
if (users[user]) {
return callback.call(that, user, users[user].groups, users[user].acl);
}
// Read all groups
this.getObjectList({startkey: 'system.group.', endkey: 'system.group.\u9999'}, {checked: true}, function (err, arr) {
if (err) log.error(namespace + ' ' + err);
groups = [];
if (arr) {
// Read all groups
for (let g = 0; g < arr.rows.length; g++) {
groups[g] = arr.rows[g].value;
if (groups[g]._id === 'system.group.administrator') {
groups[g].common.acl = {
file: {
list: true,
read: true,
write: true,
create: true,
'delete': true
},
object: {
list: true,
read: true,
write: true,
create: true,
'delete': true
},
users: {
list: true,
read: true,
write: true,
create: true,
'delete': true
}
};
}
}
}
that.getObjectList({startkey: 'system.user.', endkey: 'system.user.\u9999'}, {checked: true}, function (err, arr) {
if (err) log.error(namespace + ' ' + err);
users = {};
if (arr) {
for (let i = 0; i < arr.rows.length; i++) {
users[arr.rows[i].value._id] = Object.assign({}, defaultAcl);
if (arr.rows[i].value._id === 'system.user.admin') {
users['system.user.admin'].acl.file = {
list: true,
read: true,
write: true,
create: true,
'delete': true
};
users['system.user.admin'].acl.object = {
create: true,
list: true,
read: true,
write: true,
'delete': true
};
users['system.user.admin'].acl.users = {
create: true,
list: true,
read: true,
write: true,
'delete': true
};
}
}
}
for (let g = 0; g < groups.length; g++) {
if (!groups[g].common.members) continue;
for (let m = 0; m < groups[g].common.members.length; m++) {
let u = groups[g].common.members[m];
if (!users[u]) {
log.warn('Unknown user in group "' + g + '": ' + u);
continue;
}
users[u].groups.push(groups[g]._id);
if (groups[g].common.acl && groups[g].common.acl.file) {
if (!users[u].acl || !users[u].acl.file) {
users[u].acl = users[u].acl || {};
users[u].acl.file = users[u].acl.file || {};
users[u].acl.file.create = groups[g].common.acl.file.create;
users[u].acl.file.read = groups[g].common.acl.file.read;
users[u].acl.file.write = groups[g].common.acl.file.write;
users[u].acl.file['delete'] = groups[g].common.acl.file['delete'];
users[u].acl.file.list = groups[g].common.acl.file.list;
} else {
users[u].acl.file.create = users[u].acl.file.create || groups[g].common.acl.file.create;
users[u].acl.file.read = users[u].acl.file.read || groups[g].common.acl.file.read;
users[u].acl.file.write = users[u].acl.file.write || groups[g].common.acl.file.write;
users[u].acl.file['delete'] = users[u].acl.file['delete'] || groups[g].common.acl.file['delete'];
users[u].acl.file.list = users[u].acl.file.list || groups[g].common.acl.file.list;
}
}
if (groups[g].common.acl && groups[g].common.acl.object) {
if (!users[u].acl || !users[u].acl.object) {
users[u].acl = users[u].acl || {};
users[u].acl.object = users[u].acl.object || {};
users[u].acl.object.create = groups[g].common.acl.object.create;
users[u].acl.object.read = groups[g].common.acl.object.read;
users[u].acl.object.write = groups[g].common.acl.object.write;
users[u].acl.object['delete'] = groups[g].common.acl.object['delete'];
users[u].acl.object.list = groups[g].common.acl.object.list;
} else {
users[u].acl.object.create = users[u].acl.object.create || groups[g].common.acl.object.create;
users[u].acl.object.read = users[u].acl.object.read || groups[g].common.acl.object.read;
users[u].acl.object.write = users[u].acl.object.write || groups[g].common.acl.object.write;
users[u].acl.object['delete'] = users[u].acl.object['delete'] || groups[g].common.acl.object['delete'];
users[u].acl.object.list = users[u].acl.object.list || groups[g].common.acl.object.list;
}
}
if (groups[g].common.acl && groups[g].common.acl.users) {
if (!users[u].acl || !users[u].acl.users) {
users[u].acl = users[u].acl || {};
users[u].acl.users = users[u].acl.users || {};
users[u].acl.users.create = groups[g].common.acl.users.create;
users[u].acl.users.read = groups[g].common.acl.users.read;
users[u].acl.users.write = groups[g].common.acl.users.write;
users[u].acl.users['delete'] = groups[g].common.acl.users['delete'];
users[u].acl.users.list = groups[g].common.acl.users.list;
} else {
users[u].acl.users.create = users[u].acl.users.create || groups[g].common.acl.users.create;
users[u].acl.users.read = users[u].acl.users.read || groups[g].common.acl.users.read;
users[u].acl.users.write = users[u].acl.users.write || groups[g].common.acl.users.write;
users[u].acl.users['delete'] = users[u].acl.users['delete'] || groups[g].common.acl.users['delete'];
users[u].acl.users.list = users[u].acl.users.list || groups[g].common.acl.users.list;
}
}
}
}
callback.call(that, user, users[user] ? users[user].groups : [], users[user] ? users[user].acl : Object.assign({}, defaultAcl.acl));
});
});
};
this.getMimeType = function (ext) {
if (ext instanceof Array) ext = ext[0];
let _mimeType = 'text/javascript';
let isBinary = false;
if (ext === '.css') {
_mimeType = 'text/css';
} else if (ext === '.ico') {
_mimeType = 'image/x-icon';
isBinary = true;
} else if (ext === '.bmp') {
_mimeType = 'image/bmp';
isBinary = true;
} else if (ext === '.png') {
isBinary = true;
_mimeType = 'image/png';
} else if (ext === '.jpg') {
isBinary = true;
_mimeType = 'image/jpeg';
} else if (ext === '.jpeg') {
isBinary = true;
_mimeType = 'image/jpeg';
} else if (ext === '.gif') {
isBinary = true;
_mimeType = 'image/gif';
} else if (ext === '.tif') {
isBinary = true;
_mimeType = 'image/tiff';
} else if (ext === '.js') {
_mimeType = 'application/javascript';
} else if (ext === '.html') {
_mimeType = 'text/html';
} else if (ext === '.htm') {
_mimeType = 'text/html';
} else if (ext === '.json') {
_mimeType = 'application/json';
} else if (ext === '.xml') {
_mimeType = 'text/xml';
} else if (ext === '.svg') {
_mimeType = 'image/svg+xml';
} else if (ext === '.eot') {
isBinary = true;
_mimeType = 'application/vnd.ms-fontobject';
} else if (ext === '.ttf') {
isBinary = true;
_mimeType = 'application/font-sfnt';
} else if (ext === '.cur') {
isBinary = true;
_mimeType = 'application/x-win-bitmap';
} else if (ext === '.woff') {
isBinary = true;
_mimeType = 'application/font-woff';
} else if (ext === '.wav') {
isBinary = true;
_mimeType = 'audio/wav';
} else if (ext === '.mp3') {
isBinary = true;
_mimeType = 'audio/mpeg3';
} else if (ext === '.avi') {
isBinary = true;
_mimeType = 'video/avi';
} else if (ext === '.mp4') {
isBinary = true;
_mimeType = 'video/mp4';
} else if (ext === '.mkv') {
isBinary = true;
_mimeType = 'video/mkv';
} else if (ext === '.zip') {
isBinary = true;
_mimeType = 'application/zip';
} else if (ext === '.ogg') {
isBinary = true;
_mimeType = 'audio/ogg';
} else if (ext === '.manifest') {
_mimeType = 'text/cache-manifest';
} else {
_mimeType = 'text/javascript';
}
return {mimeType: _mimeType, isBinary: isBinary};
};
this.insert = function (id, attName, ignore, options, obj, callback) {
if (typeof options === 'string') {
options = {mimeType: options};
}
//return pipe for write into redis
let strm = new WMStrm(id + '/' + attName);
strm.on('finish', function () {
if (!memStore[id + '/' + attName]) log.error(namespace + ' File ' + id + ' / ' + attName + ' is empty');
that.writeFile(id, attName, memStore[id + '/' + attName] || '', options, function () {
if (memStore[id + '/' + attName] !== undefined) delete memStore[id + '/' + attName];
if (callback) setImmediate(callback, null, null);
});
});
return strm;
};
this.writeFile = function (id, name, data, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (typeof options === 'string') {
options = {mimeType: options};
}
if (name[0] === '/') name = name.substring(1);
try {
if (!fileOptions[id]) {
if (fs.existsSync(objectsDir + id + '/_data.json')) {
try {
fileOptions[id] = JSON.parse(fs.readFileSync(objectsDir + id + '/_data.json', 'utf8'));
} catch (e) {
log.error(namespace + ' Cannot parse ' + objectsDir + id + '/_data.json: ' + e);
}
} else {
fileOptions[id] = {};
}
}
files[id] = files[id] || {};
// If file yet exists => check the permissions
if (!options || !options.checked) {
return checkFileRights(id, name, options, 0x2/*write*/, function (err, options) {
if (err) {
if (typeof callback === 'function') {
callback(err);
}
} else {
return that.writeFile(id, name, data, options, callback);
}
});
}
try {
if (!fs.existsSync(objectsDir)) fs.mkdirSync(objectsDir);
if (!fs.existsSync(objectsDir + id)) fs.mkdirSync(objectsDir + id);
} catch (e) {
log.error(namespace + ' Cannot create directories: ' + objectsDir + id + ': ' + e.message);
log.error(namespace + ' Check the permissions! Or call "sudo chmod -R 777 *" in ' + tools.appName +' dir');
if (typeof callback === 'function') callback(e.message);
return;
}
let isBinary;
let ext = name.match(/\.[^.]+$/);
let mime = that.getMimeType(ext);
let _mimeType = mime.mimeType;
isBinary = mime.isBinary;
if (!fileOptions[id][name]) {
fileOptions[id][name] = {createdAt: (new Date()).getTime()};
}
if (!fileOptions[id][name].acl) {
fileOptions[id][name].acl = {
owner: options.user || (defaultNewAcl && defaultNewAcl.owner) || 'system.user.admin',
ownerGroup: options.group || (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator',
permissions: options.mode || (defaultNewAcl && defaultNewAcl.file) || 0x644
};
}
fileOptions[id][name].mimeType = options.mimeType || _mimeType;
fileOptions[id][name].binary = isBinary;
fileOptions[id][name].acl.ownerGroup = fileOptions[id][name].acl.ownerGroup || (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator';
fileOptions[id][name].modifiedAt = (new Date()).getTime();
if (isBinary) {
// Reload by read
delete files[id][name];
} else {
files[id][name] = data;
}
try {
// Create directories if complex structure
mkpathSync(objectsDir + id + '/', name);
// Store file
fs.writeFileSync(objectsDir + id + '/' + name, data, {'flag': 'w', 'encoding': isBinary ? 'binary' : 'utf8'});
// Store dir description
saveFileSettings(id);
} catch (e) {
log.error(namespace + ' Cannot write files: ' + objectsDir + id + '/' + name + ': ' + e.message);
if (typeof callback === 'function') callback(e.message);
return;
}
if (typeof callback === 'function') callback();
} catch (e) {
if (typeof callback === 'function') callback(e.message);
}
};
this.readFile = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (name[0] === '/') name = name.substring(1);
if (!options || !options.checked) {
checkFileRights(id, name, options, 0x4/*read*/, function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
return that.readFile(id, name, options, callback);
}
});
return;
}
try {
if (!fileOptions[id]) {
if (fs.existsSync(objectsDir + id + '/_data.json')) {
try {
fileOptions[id] = JSON.parse(fs.readFileSync(objectsDir + id + '/_data.json', 'binary'));
} catch (e) {
log.error(namespace + ' Cannot parse ' + objectsDir + id + '/_data.json: ' + e);
fileOptions[id] = {};
}
} else {
fileOptions[id] = {};
}
}
if (!files[id]) files[id] = {};
if (!files[id][name] || settings.connection.noFileCache || options.noFileCache) {
if (fs.existsSync(objectsDir + id + '/' + name)) {
// Create description object if not exists
if (!fileOptions[id][name]) {
fileOptions[id][name] = {
acl: {
owner: (defaultNewAcl && defaultNewAcl.owner) || 'system.user.admin',
ownerGroup: (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator',
permissions: (defaultNewAcl && defaultNewAcl.file.permissions) || 0x777
}
};
}
if (typeof fileOptions[id][name] !== 'object') {
fileOptions[id][name] = {
mimeType: fileOptions[id][name],
acl: {
owner: (defaultNewAcl && defaultNewAcl.owner) || 'system.user.admin',
ownerGroup: (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator',
permissions: (defaultNewAcl && defaultNewAcl.file.permissions) || 0x777
}
};
}
files[id][name] = fs.readFileSync(objectsDir + id + '/' + name);
if (fileOptions[id][name].binary === undefined) {
let pos = name.lastIndexOf('.');
let ext = '';
if (pos !== -1) ext = name.substring(pos);
let mimeType = that.getMimeType(ext);
fileOptions[id][name].binary = mimeType.isBinary;
fileOptions[id][name].mimeType = mimeType.mimeType;
}
if (!fileOptions[id][name].binary) {
if (files[id][name]) files[id][name] = files[id][name].toString();
}
} else {
if (fileOptions[id][name] !== undefined) delete fileOptions[id][name];
if (files[id][name] !== undefined) delete files[id][name];
}
}
if (fileOptions[id][name] && !fileOptions[id][name].acl) {
// all files belongs to admin by default, but everyone can edit it
fileOptions[id][name].acl = {
owner: (defaultNewAcl && defaultNewAcl.owner) || 'system.user.admin',
ownerGroup: (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator',
permissions: (defaultNewAcl && defaultNewAcl.file.permissions) || 0x677
};
}
if (typeof callback === 'function') {
if (fileOptions[id][name] !== null && fileOptions[id][name] !== undefined) {
if (!fileOptions[id][name].mimeType) {
let _pos = name.lastIndexOf('.');
let _ext = '';
if (_pos !== -1) _ext = name.substring(_pos);
let _mimeType = that.getMimeType(_ext);
fileOptions[id][name].mimeType = _mimeType.mimeType;
}
callback(null, files[id][name], fileOptions[id][name].mimeType);
} else {
callback('Not exists');
}
}
} catch (e) {
if (typeof callback === 'function') {
callback(e.message);
}
}
};
this.unlink = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (name[0] === '/') name = name.substring(1);
if (!options || !options.checked) {
checkFileRights(id, name, options, 0x2/*write*/, function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
if (!options.acl.file['delete']) {
if (typeof callback === 'function') callback('permissionError');
} else {
return that.unlink(id, name, options, callback);
}
}
});
return;
}
try {
let changed = false;
if (!fileOptions[id]) {
if (fs.existsSync(objectsDir + id + '/_data.json')) {
fileOptions[id] = JSON.parse(fs.readFileSync(objectsDir + id + '/_data.json', 'utf8'));
} else {
fileOptions[id] = {};
}
}
if (fileOptions[id][name]) {
changed = true;
delete fileOptions[id][name];
}
if (files[id] && files[id][name]) {
delete files[id][name];
}
if (fs.existsSync(objectsDir + id + '/' + name)) {
let stat = fs.statSync(objectsDir + id + '/' + name);
if (stat.isDirectory()) {
// read all entries and delete every one
let fdir = fs.readdirSync(objectsDir + id + '/' + name);
let cnt = 0;
for (let f = 0; f < fdir.length; f++) {
cnt++;
that.unlink(id, name + '/' + fdir[f], options, function (err) {
if (!--cnt) {
log.debug('Delete directory ' + id + '/' + name);
try {
fs.rmdirSync(objectsDir + id + '/' + name);
} catch (e) {
log.error('Cannot delete directory "' + id + '/' + name + '": ' + e);
}
if (typeof callback === 'function') {
setImmediate(function () {
callback(err);
});
}
}
});
}
if (!cnt) {
log.debug('Delete directory ' + id + '/' + name);
try {
fs.rmdirSync(objectsDir + id + '/' + name);
} catch (e) {
log.error('Cannot delete directory "' + id + '/' + name + '": ' + e);
}
if (typeof callback === 'function') {
setImmediate(function () {
callback();
});
}
}
} else {
log.debug('Delete file ' + id + '/' + name);
try {
fs.unlinkSync(objectsDir + id + '/' + name);
} catch (e) {
log.error('Cannot delete file "' + id + '/' + name + '": ' + e);
}
if (typeof callback === 'function') {
setImmediate(function () {
callback();
});
}
}
} else {
if (typeof callback === 'function') {
setImmediate(function () {
callback('Not exists');
});
}
}
// Store dir description
if (changed) saveFileSettings(id);
} catch (e) {
if (typeof callback === 'function') {
setImmediate(function () {
callback(e.message);
});
}
}
};
this.delFile = this.unlink;
this.readDir = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!options || !options.checked) {
checkFileRights(id, name, options, 0x4/*read*/, function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
if (!options.acl.file.list) {
if (typeof callback === 'function') callback('permissionError');
} else {
return that.readDir(id, name, options, callback);
}
}
});
return;
}
if (!fileOptions[id]) {
if (fs.existsSync(objectsDir + id + '/_data.json')) {
try {
fileOptions[id] = JSON.parse(fs.readFileSync(objectsDir + id + '/_data.json', 'binary'));
} catch (e) {
log.error(namespace + ' Cannot parse ' + objectsDir + id + '/_data.json: ' + e);
}
} else {
fileOptions[id] = {};
}
}
// Find all files and directories starts with name
let _files = [];
if (name[0] === '/') name = name.substring(1);
if (name && name[name.length - 1] !== '/') name += '/';
let len = (name) ? name.length : 0;
for (let f in fileOptions[id]) {
if (fileOptions[id].hasOwnProperty(f) && (!name || f.substring(0, len) === name)) {
/** @type {string|string[]} */
let rest = f.substring(len);
rest = rest.split('/', 2);
if (rest[0] && _files.indexOf(rest[0]) === -1) {
_files.push(rest[0]);
}
}
}
if (fs.existsSync(objectsDir + id + '/' + name)) {
try {
let dirFiles = fs.readdirSync(objectsDir + id + '/' + name);
for (let i = 0; i < dirFiles.length; i++) {
if (dirFiles[i] === '..' || dirFiles[i] === '.') continue;
if (dirFiles[i] !== '_data.json' && _files.indexOf(dirFiles[i]) === -1) {
_files.push(dirFiles[i]);
}
}
} catch (e) {
if (typeof callback === 'function') {
setImmediate(function () {
callback(e, []);
});
}
return;
}
} else {
if (typeof callback === 'function') {
setImmediate(function () {
callback('Not exists', []);
});
}
return;
}
_files.sort();
let res = [];
for (let j = 0; j < _files.length; j++) {
if (_files[j] === '..' || _files[j] === '.') continue;
if (fs.existsSync(objectsDir + id + '/' + name + _files[j])) {
let stats = fs.statSync(objectsDir + id + '/' + name + _files[j]);
let acl = (fileOptions[id][name + _files[j]] && fileOptions[id][name + _files[j]].acl) ?
Object.assign({}, fileOptions[id][name + _files[j]].acl) : // copy settings
{
read: true,
write : true,
owner: (defaultNewAcl && defaultNewAcl.owner) || 'system.user.admin',
ownerGroup: (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator',
permissions: (defaultNewAcl && defaultNewAcl.file.permissions) || 0x644
};
try {
// if filter for user
if (options.filter && acl) {
// If user may not write
if (!options.acl.file.write) {// write
acl.permissions &= ~0x222;
}
// If user may not read
if (!options.acl.file.read) {// read
acl.permissions &= ~0x444;
}
if (options.user !== 'system.user.admin' && options.groups.indexOf('system.group.administrator') === -1) {
if (acl.owner !== options.user) {
// Check if the user is in the group
if (options.groups.indexOf(acl.ownerGroup) !== -1) {
// Check group rights
if (!(acl.permissions & (0x6 << 4))) {
continue;
}
acl.read = !!(acl.permissions & 0x40);
acl.write = !!(acl.permissions & 0x20);
} else {
// everybody
if (!(acl.permissions & 0x6)) {
continue;
}
acl.read = !!(acl.permissions & 0x4);
acl.write = !!(acl.permissions & 0x2);
}
} else {
// Check user rights
if (!(acl.permissions & (0x6 << 8))) {
continue;
}
acl.read = !!(acl.permissions & 0x400);
acl.write = !!(acl.permissions & 0x200);
}
} else {
acl.read = true;
acl.write = true;
}
}
} catch (e) {
log.error(namespace + ' Cannot read permssions of ' + objectsDir + id + '/' + name + _files[j] + ': ' + e);
}
res.push({
file: _files[j],
stats: stats,
isDir: stats.isDirectory(),
acl: acl,
modifiedAt: fileOptions[id][name + _files[j]] ? fileOptions[id][name + _files[j]].modifiedAt : undefined,
createdAt: fileOptions[id][name + _files[j]] ? fileOptions[id][name + _files[j]].createdAt : undefined
});
}
}
if (typeof callback === 'function') {
setImmediate(function () {
callback(null, res);
});
}
};
this.rename = function (id, oldName, newName, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (oldName[0] === '/') oldName = oldName.substring(1);
if (newName[0] === '/') newName = newName.substring(1);
if (!options || !options.checked) {
checkFileRights(id, oldName, options, 0x2/*write*/, function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
if (!options.acl.file.write) {
if (typeof callback === 'function') callback('permissionError');
} else {
return that.rename(id, oldName, newName, options, callback);
}
}
});
return;
}
try {
if (!fileOptions[id]) {
if (fs.existsSync(objectsDir + id + '/_data.json')) {
fileOptions[id] = JSON.parse(fs.readFileSync(objectsDir + id + '/_data.json', 'utf8'));
} else {
fileOptions[id] = {};
}
}
if (fileOptions[id][oldName]) {
let type = fileOptions[id][oldName];
delete fileOptions[id][oldName];
fileOptions[id][newName] = type;
fs.writeFileSync(objectsDir + id + '/_data.json', JSON.stringify(fileOptions[id]));
}
if (files[id] && files[id][oldName]) {
let data = files[id][oldName];
delete files[id][oldName];
files[id][newName] = data;
}
if (fs.existsSync(objectsDir + id + '/' + oldName)) {
fs.renameSync(objectsDir + id + '/' + oldName, objectsDir + id + '/' + newName);
if (typeof callback === 'function') callback();
} else {
if (typeof callback === 'function') callback('Not exists');
}
} catch (e) {
if (typeof callback === 'function') callback(e.message);
}
};
this.touch = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!options || !options.checked) {
checkFileRights(id, null, options, 0x2/*write*/, function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
return that.touch(id, name, options, callback);
}
});
return;
}
try {
if (!fileOptions[id]) {
if (fs.existsSync(objectsDir + id + '/_data.json')) {
fileOptions[id] = JSON.parse(fs.readFileSync(objectsDir + id + '/_data.json', 'utf8'));
} else {
fileOptions[id] = {};
}
}
let regEx = new RegExp(pattern2RegEx(name));
let processed = [];
let now = (new Date()).getTime();
let changed = false;
for (let f in fileOptions[id]) {
if (!fileOptions[id].hasOwnProperty(f)) continue;
if (regEx.test(f) && checkFile(id, f, options, 2/*write*/)) {
changed = true;
// Check if file exists
if (fs.existsSync(objectsDir + id + '/' + f)) {
if (!fileOptions[id][f]) {
fileOptions[id][f] = {};
fileOptions[id][f].createdAt = now;
}
if (typeof fileOptions[id][f] !== 'object') {
fileOptions[id][f] = {
mimeType: fileOptions[id][f]
};
}
if (!fileOptions[id][f].mimeType) {
let pos = f.lastIndexOf('.');
let ext = '';
if (pos !== -1) ext = f.substring(pos);
let mimeType = that.getMimeType(ext);
fileOptions[id][f].binary = mimeType.isBinary;
fileOptions[id][f].mimeType = mimeType.mimeType;
}
if (!fileOptions[id][f].acl) {
fileOptions[id][f].acl = {
owner: (defaultNewAcl && defaultNewAcl.owner) || 'system.user.admin',
ownerGroup: (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator',
permissions: (defaultNewAcl && defaultNewAcl.file) || 0x644 // '0644'
};
}
let fOp = fileOptions[id][f];
fOp.modifiedAt = now;
let stats = fs.statSync(objectsDir + id + '/' + f);
let parts = f.split('/');
let fileName = parts.pop();
processed.push({
path: parts.join('/'),
file: fileName,
stats: stats,
isDir: stats.isDirectory(),
acl: fOp.acl || {},
modifiedAt: fOp.modifiedAt,
createdAt: fOp.createdAt
});
} else {
delete fileOptions[id][f];
}
}
}
// Store dir description
if (changed) fs.writeFileSync(objectsDir + id + '/_data.json', JSON.stringify(fileOptions[id]));
if (typeof callback === 'function') callback(null, processed);
} catch (e) {
if (typeof callback === 'function') callback(e.message);
}
};
this.rm = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!options || !options.checked) {
checkFileRights(id, null, options, 0x2/*write*/, function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
if (!options.acl.file['delete']) {
if (typeof callback === 'function') callback('permissionError');
} else {
return that.rm(id, name, options, callback);
}
}
});
return;
}
try {
if (!fileOptions[id]) {
if (fs.existsSync(objectsDir + id + '/_data.json')) {
fileOptions[id] = JSON.parse(fs.readFileSync(objectsDir + id + '/_data.json', 'utf8'));
} else {
fileOptions[id] = {};
}
}
let regEx = new RegExp(pattern2RegEx(name));
let processed = [];
let changed = false;
let dirs = [];
for (let f in fileOptions[id]) {
if (!fileOptions[id].hasOwnProperty(f)) continue;
if (regEx.test(f) && checkFile(id, f, options, 2/*write*/)) {
let stat;
if (fileOptions[id][f]) {
changed = true;
delete fileOptions[id][f];
}
if (files && files[id] && files[id][f]) {
delete files[id][f];
}
if (fs.existsSync(objectsDir + id + '/' + f)) {
stat = fs.statSync(objectsDir + id + '/' + f);
if (stat.isDirectory()) {
if (dirs.indexOf(f) === -1) dirs.push(f);
} else {
fs.unlinkSync(objectsDir + id + '/' + f);
}
}
let parts = f.split('/');
let fileName = parts.pop();
let path = parts.join('/');
if (dirs.indexOf(path) === -1) dirs.push(path);
processed.push({
path: path,
file: fileName,
isDir: stat && stat.isDirectory()
});
}
}
// try to delete directories
for (let d = 0; d < dirs.length; d++) {
try {
let _files = fs.readdirSync(objectsDir + id + '/' + dirs[d]);
if (_files.length) {
console.log('Directory ' + id + '/' + dirs[d] + ' is not empty');
} else {
fs.rmdirSync(objectsDir + id + '/' + dirs[d]);
}
} catch (e) {
console.error('Cannot delete ' + id + '/' + dirs[d] + ': ' + e);
}
}
// Store dir description
if (changed) fs.writeFileSync(objectsDir + id + '/_data.json', JSON.stringify(fileOptions[id]));
if (typeof callback === 'function') callback(null, processed);
} catch (e) {
if (typeof callback === 'function') callback(e.message);
}
};
this.mkdir = function (id, dirname, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (dirname[0] === '/') dirname = dirname.substring(1);
if (!options || !options.checked) {
checkFileRights(id, dirname, options, 0x2/*write*/, function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
if (!options.acl.file.write) {
if (typeof callback === 'function') callback('permissionError');
} else {
return that.mkdir(id, dirname, options, callback);
}
}
});
return;
}
try {
if (!fileOptions[id]) {
if (fs.existsSync(objectsDir + id + '/_data.json')) {
fileOptions[id] = JSON.parse(fs.readFileSync(objectsDir + id + '/_data.json', 'utf8'));
} else {
fileOptions[id] = {};
}
}
if (!fs.existsSync(objectsDir + id + '/' + dirname)) {
fs.mkdirSync(objectsDir + id + '/' + dirname);
if (typeof callback === 'function') callback();
} else {
if (typeof callback === 'function') callback('Yet exists');
}
} catch (e) {
if (typeof callback === 'function') callback(e.message);
}
};
this.chownFile = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
if (typeof options !== 'object') {
options = {owner: options};
}
if (name[0] === '/') name = name.substring(1);
if (!options.ownerGroup && options.group) options.ownerGroup = options.group;
if (!options.owner && options.user) options.owner = options.user;
if (!options.owner) {
log.error(namespace + ' user is not defined');
if (typeof callback === 'function') callback('invalid parameter');
return;
}
if (!options.ownerGroup) {
// get user group
this.getUserGroup(options.owner, function (user, groups /* , permissions */) {
if (!groups || !groups[0]) {
if (typeof callback === 'function') callback('user "' + options.owner + '" belongs to no group');
return;
} else {
options.ownerGroup = groups[0];
}
that.chownFile(id, name, options, callback);
});
return;
}
if (!options.checked) {
checkFileRights(id, null, options, 0x2/* write */, function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
if (!options.acl.file.write) {
if (typeof callback === 'function') callback('permissionError');
} else {
return that.chownFile(id, name, options, callback);
}
}
});
return;
}
try {
if (!fileOptions[id]) {
if (fs.existsSync(objectsDir + id + '/_data.json')) {
try {
fileOptions[id] = JSON.parse(fs.readFileSync(objectsDir + id + '/_data.json', 'binary'));
} catch (e) {
log.error(namespace + ' Cannot parse ' + objectsDir + id + '/_data.json: ' + e);
}
} else {
fileOptions[id] = {};
}
}
let regEx = new RegExp(pattern2RegEx(name));
let processed = [];
let changed = false;
for (let f in fileOptions[id]) {
if (!fileOptions[id].hasOwnProperty(f)) continue;
if (regEx.test(f) && checkFile(id, f, options, 2/*write*/)) {
changed = true;
if (typeof fileOptions[id][f] !== 'object') {
fileOptions[id][f] = {
mimeType: fileOptions[id][f]
};
}
if (!fileOptions[id][f].acl) {
fileOptions[id][f].acl = {
owner: (defaultNewAcl && defaultNewAcl.owner) || 'system.user.admin',
ownerGroup: (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator',
permissions: (defaultNewAcl && defaultNewAcl.file) || 0x644 // '0644'
};
}
fileOptions[id][f].acl.owner = options.owner;
fileOptions[id][f].acl.ownerGroup = options.ownerGroup;
if (fs.existsSync(objectsDir + id + '/' + f)) {
let stats = fs.statSync(objectsDir + id + '/' + f);
let acl = fileOptions[id][f];
let parts = f.split('/');
let fileName = parts.pop();
processed.push({
path: parts.join('/'),
file: fileName,
stats: stats,
isDir: stats.isDirectory(),
acl: acl.acl || {},
modifiedAt: fileOptions[id][f].modifiedAt,
createdAt: fileOptions[id][f].createdAt
});
}
}
}
// Store dir description
if (changed) fs.writeFileSync(objectsDir + id + '/_data.json', JSON.stringify(fileOptions[id]));
if (typeof callback === 'function') {
setImmediate(function () {
callback(null, processed, id);
});
}
} catch (e) {
if (typeof callback === 'function') {
setImmediate(function () {
callback(e.message);
});
}
}
};
this.chmodFile = function (id, name, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
if (name[0] === '/') name = name.substring(1);
if (typeof options !== 'object') {
options = {mode: options};
}
if (options.mode === undefined) {
log.error(namespace + ' mode is not defined');
if (typeof callback === 'function') callback('invalid parameter');
return;
} else if (typeof options.mode === 'string') {
options.mode = parseInt(options.mode, 16);
}
if (!options.checked) {
checkFileRights(id, null, options, 0x2/*write*/, function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
if (!options.acl.file.write) {
if (typeof callback === 'function') callback('permissionError');
} else {
return that.chmodFile(id, name, options, callback);
}
}
});
return;
}
try {
if (!fileOptions[id]) {
if (fs.existsSync(objectsDir + id + '/_data.json')) {
try {
fileOptions[id] = JSON.parse(fs.readFileSync(objectsDir + id + '/_data.json', 'binary'));
} catch (e) {
log.error(namespace + ' Cannot parse ' + objectsDir + id + '/_data.json: ' + e);
}
} else {
fileOptions[id] = {};
}
}
let regEx = new RegExp(pattern2RegEx(name));
let processed = [];
let changed = false;
for (let f in fileOptions[id]) {
if (!fileOptions[id].hasOwnProperty(f)) continue;
if (regEx.test(f) && checkFile(id, f, options, 2/*write*/)) {
changed = true;
if (typeof fileOptions[id][f] !== 'object') {
fileOptions[id][f] = {
mimeType: fileOptions[id][f]
};
}
if (!fileOptions[id][f].acl) {
fileOptions[id][f].acl = {
owner: (defaultNewAcl && defaultNewAcl.owner) || 'system.user.admin',
ownerGroup: (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator',
permissions: (defaultNewAcl && defaultNewAcl.file) || 0x644 // '0644'
};
}
fileOptions[id][f].acl.permissions = options.mode;
if (fs.existsSync(objectsDir + id + '/' + f)) {
let stats = fs.statSync(objectsDir + id + '/' + f);
let acl = fileOptions[id][f];
let parts = f.split('/');
let fileName = parts.pop();
processed.push({
path: parts.join('/'),
file: fileName,
stats: stats,
isDir: stats.isDirectory(),
acl: acl.acl || {},
modifiedAt: acl.modifiedAt,
createdAt: acl.createdAt
});
}
}
}
// Store dir description
if (changed) fs.writeFileSync(objectsDir + id + '/_data.json', JSON.stringify(fileOptions[id]));
if (typeof callback === 'function') {
setImmediate(function () {
callback(null, processed, id);
});
}
} catch (e) {
if (typeof callback === 'function') {
setImmediate(function () {
callback(e.message);
});
}
}
};
this.enableFileCache = function (enabled, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!options || !options.checked) {
checkObjectRights(null, options, 0x2/*write*/, function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
return this.enableFileCache(enabled, options, callback);
}
}.bind(this));
return;
}
if (settings.connection.noFileCache !== enabled) {
settings.connection.noFileCache = !!enabled;
if (!settings.connection.noFileCache) {
// clear cache
files = {};
}
}
if (typeof callback === 'function') {
setImmediate(function () {
callback(null, settings.connection.noFileCache);
});
}
};
// -------------- OBJECT FUNCTIONS -------------------------------------------
function checkObject(id, options, flag) {
// read rights of object
if (!objects[id] || !objects[id].common || !objects[id].acl || flag === 'list') {
return true;
}
if (options.user !== 'system.user.admin' &&
options.groups && options.groups.indexOf('system.group.administrator') === -1) {
if (objects[id].acl.owner !== options.user) {
// Check if the user is in the group
if (options.groups.indexOf(objects[id].acl.ownerGroup) !== -1) {
// Check group rights
if (!(objects[id].acl.object & (flag << 4))) {
return false;
}
} else {
// everybody
if (!(objects[id].acl.object & flag)) {
return false;
}
}
} else {
// Check group rights
if (!(objects[id].acl.object & (flag << 8))) {
return false;
}
}
}
return true;
}
function checkObjectRights(id, options, flag, callback) {
options = options || {};
if (!options.user) {
// Before files converted, lets think: if no options it is admin
options = {
user: 'system.user.admin',
params: options,
group: 'system.group.administrator',
acl: {
object: {
read: true,
write: true,
'delete': true,
create: true,
list: true
},
file: {
read: true,
write: true,
'delete': true,
create: true,
list: true
},
/* state: {
read: true,
write: true,
'delete': true,
create: true,
list: true
},*/
users: {
read: true,
write: true,
create: true,
'delete': true,
list: true
}
}
};
}
if (options.checked) {
return callback(null, options);
}
if (!options.acl) {
that.getUserGroup(options.user, function (user, groups, acl) {
options.acl = acl || {};
options.groups = groups;
options.group = groups ? groups[0] : null;
checkObjectRights(id, options, flag, callback);
});
return;
}
// if user or group objects
if (regUser.test(id) || regGroup.test(id)) {
// If user may write
if (flag === 2 && !options.acl.users.write) {// write
return callback('permissionError', options);
}
// If user may read
if (flag === 4 && !options.acl.users.read) {// read
return callback('permissionError', options);
}
// If user may delete
if (flag === 'delete' && !options.acl.users.delete) {// delete
return callback('permissionError', options);
}
// If user may list
if (flag === 'list' && !options.acl.users.list) {// list
return callback('permissionError', options);
}
// If user may create
if (flag === 'create' && !options.acl.users.create) {// create
return callback('permissionError', options);
}
if (flag === 'delete') flag = 2; // write
}
// If user may write
if (flag === 2 && !options.acl.object.write) {// write
return callback('permissionError', options);
}
// If user may read
if (flag === 4 && !options.acl.object.read) {// read
return callback('permissionError', options);
}
// If user may delete
if (flag === 'delete' && !options.acl.object.delete) {// delete
return callback('permissionError', options);
}
// If user may list
if (flag === 'list' && !options.acl.object.list) {// list
return callback('permissionError', options);
}
if (flag === 'delete') flag = 2; // write
options.checked = true;
if (id && !checkObject(id, options, flag)) {
return callback('permissionError', options);
}
return callback(null, options);
}
function clone(obj) {
if (obj === null || obj === undefined || typeof obj !== 'object') {
return obj;
}
let temp = obj.constructor(); // changed
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
temp[key] = clone(obj[key]);
}
}
return temp;
}
function pattern2RegEx(pattern) {
if (pattern !== '*') {
if (pattern[0] === '*' && pattern[pattern.length - 1] !== '*') pattern += '$';
if (pattern[0] !== '*' && pattern[pattern.length - 1] === '*') pattern = '^' + pattern;
}
pattern = pattern.replace(/\./g, '\\.');
pattern = pattern.replace(/\*/g, '.*');
return pattern;
}
function deleteOldBackupFiles() {
// delete files only if settings.backupNumber is not 0
let files = fs.readdirSync(backupDir);
files.sort();
const limit = Date.now() - settings.backup.hours * 3600000;
for (let f = files.length - 1; f >= 0; f--) {
if (!files[f].match(/_objects.json.gz$/)) {
files.splice(f, 1);
}
}
while (files.length > settings.backup.files) {
let file = files.shift();
// extract time
const ms = new Date(file.substring(0, 10) + ' ' + file.substring(11, 16).replace('-', ':') + ':00').getTime();
if (limit > ms) {
try {
fs.unlink(backupDir + file);
} catch (e) {
log.error(`Cannot delete file "${backupDir + file}: ${JSON.stringify(e)}`);
}
}
}
}
function getTimeStr(date) {
let dateObj = new Date(date);
let text = dateObj.getFullYear().toString() + '-';
let v = dateObj.getMonth() + 1;
if (v < 10) text += '0';
text += v.toString() + '-';
v = dateObj.getDate();
if (v < 10) text += '0';
text += v.toString() + '_';
v = dateObj.getHours();
if (v < 10) text += '0';
text += v.toString() + '-';
v = dateObj.getMinutes();
if (v < 10) text += '0';
text += v.toString();
return text;
}
function saveConfig() {
if (fs.existsSync(objectsName)) {
let old = fs.readFileSync(objectsName);
fs.writeFileSync(objectsName + '.bak', old);
}
try {
const actual = JSON.stringify(objects);
fs.writeFileSync(objectsName, actual);
if (!settings.backup.disabled) {
// save files for the last x hours
const now = Date.now();
// makes backups only if settings.backupInterval is not 0
if (settings.backup.period && (!lastSave || now - lastSave > settings.backup.period)) {
lastSave = now;
let backFileName = backupDir + getTimeStr(now) + '_objects.json.gz';
if (!fs.existsSync(backFileName)) {
zlib = zlib || require('zlib');
let output = fs.createWriteStream(backFileName);
let compress = zlib.createGzip();
/* The following line will pipe everything written into compress to the file stream */
compress.pipe(output);
/* Since we're piped through the file stream, the following line will do:
'Hello World!'->gzip compression->file which is the desired effect */
compress.write(actual);
compress.end();
// analyse older files
deleteOldBackupFiles();
}
}
}
} catch (e) {
log.error(namespace + ' Cannot save file ' + objectsName + ': ' + e);
}
if (configTimer) {
clearTimeout(configTimer);
configTimer = null;
}
}
function subscribe(socket, type, pattern, options) {
socket._subscribe = socket._subscribe || {};
let s = socket._subscribe[type] = socket._subscribe[type] || [];
for (let i = 0; i < s.length; i++) {
if (s[i].pattern === pattern) return;
}
s.push({pattern: pattern, regex: new RegExp(pattern2RegEx(pattern)), options: options});
}
function unsubscribe(socket, type, pattern /*, options */) {
if (!socket._subscribe || !socket._subscribe[type]) return;
let s = socket._subscribe[type];
for (let i = 0; i < s.length; i++) {
if (s[i].pattern === pattern) {
s.splice(i, 1);
return;
}
}
}
function publish(socket, type, id, obj) {
if (!socket._subscribe || !socket._subscribe[type]) return;
let s = socket._subscribe[type];
for (let i = 0; i < s.length; i++) {
if (s[i].regex.test(id)) {
socket.emit('message', s[i].pattern, id, obj);
return;
}
}
}
function publishAll(type, id, obj) {
if (id === undefined) {
console.log('Problem');
}
let clients = server.io.sockets.connected;
for (let i in clients) {
if (clients.hasOwnProperty(i)) {
publish(clients[i], type, id, obj);
}
}
if (change && that._subscribe && that._subscribe[type]) {
for (let j = 0; j < that._subscribe[type].length; j++) {
if (that._subscribe[type][j].regex.test(id)) {
setImmediate(change, id, obj);
break;
}
}
}
}
/*
function storeHistory(id, obj) {
let parts = id.split('.');
let date = parts.pop();
let file = historyName + date + '/' + parts.join('.') + '.json';
if (!fs.existsSync(historyName + date)) fs.mkdirSync(historyName + date);
fs.writeFileSync(file, JSON.stringify(obj.common.data, null, 2));
delete obj.common.data;
}
function today() {
let dateObj = new Date();
let text = dateObj.getFullYear().toString();
let v = dateObj.getMonth() + 1;
if (v < 10) text += '0';
text += v.toString();
v = dateObj.getDate();
if (v < 10) text += '0';
text += v.toString();
return text;
}
function loadHistory(id, obj) {
let parts = id.split('.');
let date = parts.pop();
let file = historyName + date + '/' + parts.join('.') + '.json';
if (fs.existsSync(file)) {
if (!obj || !obj.common) {
obj = {
type: 'history',
common: {
source: id,
day: date,
data: []
},
native: {}
};
}
try {
obj.common.data = JSON.parse(fs.readFileSync(file));
} catch (e) {
log.error(namespace + ' Cannot parse file ' + file + ': ' + e.message);
obj.common.data = [];
}
}
}*/
this.subscribeConfig = function (pattern, options, callback) {
if (!options || !options.checked) {
let socket = this;
checkObjectRights(null, options, 'list', function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
return that.subscribeConfig.call(socket, pattern, options, callback);
}
}.bind(this));
return;
}
subscribe(this, 'objects', pattern, options);
if (typeof callback === 'function') {
setImmediate(function () {
callback();
})
}
};
this.subscribe = this.subscribeConfig;
this.unsubscribeConfig = function (pattern, options, callback) {
if (!options || !options.checked) {
let socket = this;
checkObjectRights(null, options, 'list', function (err, options) {
if (err) {
if (typeof callback === 'function')callback(err);
} else {
return that.unsubscribeConfig.call(socket, pattern, options, callback);
}
}.bind(this));
return;
}
unsubscribe(this, 'objects', pattern);
if (typeof callback === 'function') {
setImmediate(function () {
callback();
});
}
};
this.unsubscribe = this.unsubscribeConfig;
this.chownObject = function (pattern, options, callback) {
options = options || {};
if (typeof options !== 'object') {
options = {owner: options};
}
if (!options.ownerGroup && options.group) options.ownerGroup = options.group;
if (!options.owner && options.user) options.owner = options.user;
if (!options.owner) {
log.error(namespace + ' user is not defined');
if (typeof callback === 'function') callback('invalid parameter');
return;
}
if (!options.ownerGroup) {
// get user group
this.getUserGroup(options.owner, function (user, groups /* , permissions*/) {
if (!groups || !groups[0]) {
if (typeof callback === 'function') callback('user "' + options.owner + '" belongs to no group');
return;
} else {
options.ownerGroup = groups[0];
}
that.chownObject(pattern, options, callback);
});
return;
}
if (!options.checked) {
checkObjectRights(null, options, 0x2/*write*/, function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
if (!options.acl.object || !options.acl.object.write) {
if (typeof callback === 'function') callback('permissionError');
} else {
return that.chownObject(pattern, options, callback);
}
}
});
return;
}
this.getConfigKeys(pattern, options, function (err, keys) {
if (err) {
if (typeof callback === 'function') callback(err);
return;
}
let list = [];
for (let k = 0; k < keys.length; k++) {
if (!checkObject(keys[k], options, 2/*write*/)) continue;
if (!objects[keys[k]].acl) {
objects[keys[k]].acl = {
owner: (defaultNewAcl && defaultNewAcl.owner) || 'system.user.admin',
ownerGroup: (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator',
object: (defaultNewAcl && defaultNewAcl.object) || 0x644 // '0644'
};
if (objects[keys[k]].type === 'state') {
objects[keys[k]].acl.state = (defaultNewAcl && defaultNewAcl.state) || 0x644; // '0644'
}
}
objects[keys[k]].acl.owner = options.owner;
objects[keys[k]].acl.ownerGroup = options.ownerGroup;
list.push(Object.assign({}, objects[keys[k]]));
}
if (typeof callback === 'function') callback(null, list);
if (!configTimer) configTimer = setTimeout(saveConfig, 5000);
});
};
this.chmodObject = function (pattern, options, callback) {
options = options || {};
if (typeof options !== 'object') {
options = {object: options};
}
if (options.mode && !options.object) options.object = options.mode;
if (options.object === undefined) {
log.error(namespace + ' mode is not defined');
if (typeof callback === 'function') callback('invalid parameter');
return;
} else if (typeof options.mode === 'string') {
options.mode = parseInt(options.mode, 16);
}
if (!options.checked) {
checkObjectRights(null, options, 0x2/*write*/, function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
if (!options.acl.file.write) {
if (typeof callback === 'function') callback('permissionError');
} else {
return that.chmodObject(pattern, options, callback);
}
}
});
return;
}
this.getConfigKeys(pattern, options, function (err, keys) {
if (err) {
if (typeof callback === 'function') callback(err);
return;
}
let list = [];
for (let k = 0; k < keys.length; k++) {
if (!checkObject(keys[k], options, 2/*write*/)) continue;
if (!objects[keys[k]].acl) {
objects[keys[k]].acl = {
owner: (defaultNewAcl && defaultNewAcl.owner) || 'system.user.admin',
ownerGroup: (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator',
object: (defaultNewAcl && defaultNewAcl.object) || 0x644 // '0644'
};
if (objects[keys[k]].type === 'state') {
objects[keys[k]].acl.state = (defaultNewAcl && defaultNewAcl.state) || 0x644; // '0644'
}
}
if (options.object !== undefined) objects[keys[k]].acl.object = options.object;
if (options.state !== undefined) objects[keys[k]].acl.state = options.state;
list.push(Object.assign({}, objects[keys[k]]));
}
if (typeof callback === 'function') callback(null, list);
if (!configTimer) configTimer = setTimeout(saveConfig, 5000);
});
};
this.getObject = function (id, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!options || !options.checked) {
checkObjectRights(id, options, 0x4/*read*/, function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
return this.getObject(id, options, callback);
}
}.bind(this));
return;
}
if (typeof callback === 'function') {
let obj = clone(objects[id]);
// Read history from file
/*if (regHistory.test(id)) {
if (!obj) obj = {};
if (!obj.common || !obj.common.data) loadHistory(id, obj);
if (obj.common && (!objects[id] || !objects[id].common)) objects[id] = obj;
// store the history for today in cache
if (obj.common && obj.common.day) {
if (today() === obj.common.day) {
objects[id].common.data = obj.common.data;
} else if (objects[id].common.data) {
delete objects[id].common.data;
}
}
}*/
if (typeof callback === 'function') {
setImmediate(callback, null, obj);
}
}
};
this.getObjectAsync = function (id, options) {
return new Promise((resolve, reject) => {
this.getObject(id, options, (err, obj) => {
if (err) {
reject(err);
} else {
resolve(obj);
}
});
});
};
this.getKeys = function (pattern, options, callback, dontModify) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!options || !options.checked) {
checkObjectRights(null, options, 'list', function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
return this.getConfigKeys(pattern, options, callback, dontModify);
}
}.bind(this));
return;
}
let r = new RegExp(pattern2RegEx(pattern));
let result = [];
for (let id in objects) {
if (!objects.hasOwnProperty(id)) continue;
if (r.test(id) && checkObject(id, options, 'list')) {
result.push(id);
}
}
result.sort();
if (typeof callback === 'function') {
setImmediate(function () {
callback(null, result);
});
}
};
this.getConfigKeys = this.getKeys;
this.getObjects = function (keys, options, callback, dontModify) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!options || !options.checked) {
checkObjectRights(null, options, 0x4/*read*/, function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
return this.getObjects(keys, options, callback, dontModify);
}
}.bind(this));
return;
}
if (!keys) {
if (typeof callback === 'function') callback('no keys', null);
return;
}
if (!keys.length) {
if (typeof callback === 'function') callback(null, []);
return;
}
let result = [];
for (let i = 0; i < keys.length; i++) {
if (checkObject(keys[i], options, 4/*read*/)) {
result.push(clone(objects[keys[i]]));
} else {
result.push({error: 'permissionError'});
}
}
if (typeof callback === 'function') {
setImmediate(function () {
callback(null, result);
});
}
};
this.getObjectsByPattern = (pattern, options, callback) => {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!options || !options.checked) {
checkObjectRights(null, options, 0x4/*read*/, (err, options) => {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
return this.getObjectsByPattern(pattern, options, callback);
}
});
return;
}
let r = new RegExp(pattern2RegEx(pattern));
let keys = [];
for (let id in objects) {
if (!objects.hasOwnProperty(id)) continue;
if (r.test(id) && checkObject(id, options, 0x4/*read*/)) {
keys.push(id);
}
}
keys.sort();
let result = [];
for (let i = 0; i < keys.length; i++) {
result.push(JSON.parse(JSON.stringify(objects[keys[i]])));
}
if (typeof callback === 'function') {
setImmediate(function () {
callback(null, result);
});
}
};
this.getObjectsByPatternAsync = (pattern, options) => {
return new Promise((resolve, reject) => {
this.getObjectsByPattern(pattern, options, (err, objs) => {
if (err) {
reject(err);
} else {
resolve(objs);
}
})
});
};
/**
* set anew or update object
*
* This function writes the object into DB
*
* @alias setObject
* @memberof objectsInMemServer
* @param {string} id ID of the object
* @param {object} obj
* @param {object} options options for access control are optional
* @param {function} callback return function
*/
this.setObject = function (id, obj, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!options || !options.checked) {
checkObjectRights(id, options, 0x2/*write*/, function (err, options) {
if (err) {
if (typeof callback === 'function') {
callback(err);
}
} else {
return this.setObject(id, obj, options, callback);
}
}.bind(this));
return;
}
if (!id || id.indexOf('*') !== -1) {
if (typeof callback === 'function') {
callback(`Invalid ID: ${id}`);
}
return;
}
if (!obj) {
log.error(namespace + ' setObject: Argument object is null');
if (typeof callback === 'function') {
callback('obj is null');
}
return;
}
obj._id = id;
if (id === 'system.config' && obj && obj.common && objects[id] && objects[id].common && JSON.stringify(obj.common.defaultNewAcl) !== JSON.stringify(objects[id].common.defaultNewAcl)) {
objects[id] = obj;
return setDefaultAcl(function () {
that.setObject(id, obj, options, callback);
});
}
if (!tools.checkNonEditable(objects[id], obj)) {
if (typeof callback === 'function') {
callback('Invalid password for update of vendor information');
}
return;
}
// do not delete common settings, like "history" or "mobile". It can be erased only with "null"
if (objects[id] && objects[id].common) {
for (let i = 0; i < preserveSettings.length; i++) {
// remove settings if desired
if (obj.common && obj.common[preserveSettings[i]] === null) {
delete obj.common[preserveSettings[i]];
continue;
}
if (objects[id].common[preserveSettings[i]] !== undefined && (!obj.common || obj.common[preserveSettings[i]] === undefined)) {
if (!obj.common) obj.common = {};
obj.common[preserveSettings[i]] = objects[id].common[preserveSettings[i]];
}
}
}
if (objects[id] && objects[id].acl && !obj.acl) {
obj.acl = objects[id].acl;
}
// add user default rights
if (defaultNewAcl && !obj.acl) {
obj.acl = Object.assign({}, defaultNewAcl);
delete obj.acl.file;
if (obj.type !== 'state') {
delete obj.acl.state;
}
if (options.owner) {
obj.acl.owner = options.owner;
if (!options.ownerGroup) {
obj.acl.ownerGroup = null;
this.getUserGroup(options.owner, function (user, groups /* , permissions */) {
if (!groups || !groups[0]) {
options.ownerGroup = (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator';
} else {
options.ownerGroup = groups[0];
}
that.setObject(id, obj, options, callback);
});
return;
}
}
}
if (defaultNewAcl && obj.acl && !obj.acl.ownerGroup && options.ownerGroup) {
obj.acl.ownerGroup = options.ownerGroup;
}
objects[id] = JSON.parse(JSON.stringify(obj));
publishAll('objects', id, obj);
if (typeof callback === 'function') {
setImmediate(function () {
callback(null, {id: id});
});
}
if (!configTimer) {
configTimer = setTimeout(saveConfig, 5000);
}
};
this.setObjectAsync = (id, obj, options) => {
return new Promise((resolve, reject) => {
this.setObject(id, obj, options, err => {
if (err) {
reject(err);
} else {
resolve();
}
})
});
};
this.delObject = function (id, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!options || !options.checked) {
checkObjectRights(id, options, 'delete', function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
return this.delObject(id, options, callback);
}
}.bind(this));
return;
}
if (objects[id]) {
if (objects[id].common && objects[id].common.dontDelete) {
if (typeof callback === 'function') {
setImmediate(function () {
callback('Object is marked as non deletable');
});
}
return;
}
delete objects[id];
publishAll('objects', id, null);
if (typeof callback === 'function') {
setImmediate(function () {
callback(null);
});
}
if (!configTimer) {
configTimer = setTimeout(saveConfig, 5000);
}
} else {
if (typeof callback === 'function') {
setImmediate(function () {
callback('Not exists');
});
}
}
};
this.delObjectAsync = function (id, options) {
return new Promise((resolve, reject) => {
this.delObject(id, options, err => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
};
this._applyView = function (func, params, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!options || !options.checked) {
checkObjectRights(null, options, 'list', function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
return this._applyView(func, params, options, callback);
}
}.bind(this));
return;
}
let result = {
rows: []
};
function _emit_(id, obj) {
result.rows.push({id: id, value: obj});
}
let f = eval('(' + func.map.replace(/emit/g, '_emit_') + ')');
for (let id in objects) {
if (!objects.hasOwnProperty(id)) continue;
if (params) {
if (params.startkey && id < params.startkey) continue;
if (params.endkey && id > params.endkey) continue;
}
if (objects[id]) {
try {
f(objects[id]);
} catch (e) {
console.log('Cannot execute map: ' + e.message);
}
}
}
// Calculate max
if (func.reduce === '_stats') {
let max = null;
for (let i = 0; i < result.rows.length; i++) {
if (max === null || result.rows[i].value > max) {
max = result.rows[i].value;
}
}
if (max !== null) {
result.rows = [{id: '_stats', value: {max: max}}];
} else {
result.rows = [];
}
}
if (typeof callback === 'function') callback(null, result);
};
this.getObjectView = function (design, search, params, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!options || !options.checked) {
checkObjectRights(null, options, 'list', function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
return this.getObjectView(design, search, params, options, callback);
}
}.bind(this));
return;
}
if (objects['_design/' + design]) {
if (objects['_design/' + design].views && objects['_design/' + design].views[search]) {
that._applyView(objects['_design/' + design].views[search], params, options, callback);
} else {
console.log('Cannot find search "' + search + '" in "' + design + '"');
if (typeof callback === 'function') callback({status_code: 404, status_text: 'Cannot find search "' + search + '" in "' + design + '"'});
}
} else {
console.log('Cannot find view "' + design + '"');
if (typeof callback === 'function') callback({status_code: 404, status_text: 'Cannot find view "' + design + '"'});
}
};
this.getObjectList = function (params, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!options || !options.checked) {
checkObjectRights(null, options, 'list', function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
return this.getObjectList(params, options, callback);
}
}.bind(this));
return;
}
// return rows with id and doc
let result = {
rows: []
};
for (let id in objects) {
if (!checkObject(id, options, 'read')) continue;
if (params) {
if (params.startkey && id < params.startkey) continue;
if (params.endkey && id > params.endkey) continue;
if (!params.include_docs && id[0] === '_') continue;
}
let obj = {id: id, value: clone(objects[id])};
obj.doc = obj.value;
if (options.sorted) {
// insert sorted
if (!result.rows.length) {
result.rows.push(obj);
} else if (obj.id <= result.rows[0].id) {
result.rows.unshift(obj);
} else if (obj.id >= result.rows[result.rows.length - 1].id) {
result.rows.push(obj);
} else {
for (let t = 1; t < result.rows.length; t++) {
if (obj.id > result.rows[t - 1].id && obj.id <= result.rows[t].id) {
result.rows.splice(t, 0, obj);
break;
}
}
}
} else {
result.rows.push(obj);
}
}
if (typeof callback === 'function') callback(null, result);
};
this.getObjectListAsync = (params, options) => {
return new Promise((resolve, reject) => {
this.getObjectList(params, options, (err, arr) => {
if (err) {
reject(err);
} else {
resolve(arr);
}
})
});
};
this.extendObject = function (id, obj, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!options || !options.checked) {
checkObjectRights(id, options, 2/*write*/, function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
return this.extendObject(id, obj, options, callback);
}
}.bind(this));
return;
}
if (!id || id.indexOf('*') !== -1) {
if (typeof callback === 'function') {
callback(`Invalid ID: ${id}`);
}
return;
}
if (id === 'system.config' && obj && obj.common && objects[id] && objects[id].common && JSON.stringify(obj.common.defaultNewAcl) !== JSON.stringify(objects[id].common.defaultNewAcl)) {
objects[id] = obj;
return setDefaultAcl(function () {
that.extendObject(id, obj, options, callback);
});
}
let oldObj;
if (objects[id] && objects[id].nonEdit) {
oldObj = Object.assign({}, objects[id])
}
objects[id] = objects[id] || {};
objects[id] = extend(true, objects[id], obj);
objects[id]._id = id;
// add user default rights
if (defaultNewAcl && !objects[id].acl) {
objects[id].acl = Object.assign({}, defaultNewAcl);
delete objects[id].acl.file;
if (objects[id].type !== 'state') {
delete objects[id].acl.state;
}
if (options.owner) {
objects[id].acl.owner = options.owner;
if (!options.ownerGroup) {
objects[id].acl.ownerGroup = null;
this.getUserGroup(options.owner, function (user, groups /*, permissions */) {
if (!groups || !groups[0]) {
options.ownerGroup = (defaultNewAcl && defaultNewAcl.ownerGroup) || 'system.group.administrator';
} else {
options.ownerGroup = groups[0];
}
that.extendObject(id, obj, options, callback);
});
return;
}
}
}
if (defaultNewAcl && options.ownerGroup && objects[id].acl && !objects[id].acl.ownerGroup) {
objects[id].acl.ownerGroup = options.ownerGroup;
}
if (oldObj && !tools.checkNonEditable(oldObj, objects[id])) {
if (typeof callback === 'function') {
callback('Invalid password for update of vendor information');
}
return;
}
publishAll('objects', id, objects[id]);
if (typeof callback === 'function') {
setImmediate(function () {
callback(null, {id: id, value: objects[id]}, id);
});
}
if (!configTimer) configTimer = setTimeout(saveConfig, 5000);
};
this.extendObjectAsync = function (id, obj, options) {
return new Promise((resolve, reject) => {
this.extendObject(id, obj, options, err => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
};
this.setConfig = this.setObject;
this.delConfig = this.delObject;
this.getConfig = this.getObject;
this.getConfigs = this.getObjects;
this.findObject = function (idOrName, type, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
if (!options || !options.checked) {
checkObjectRights(null, options, 'list', function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
return this.findObject(idOrName, type, options, callback);
}
}.bind(this));
return;
}
if (!objects) {
if (typeof callback === 'function') callback('Not implemented');
return;
}
// Assume it is ID
if (objects[idOrName] && (!type || (objects[idOrName].common && objects[idOrName].common.type === type))) {
if (typeof callback === 'function') callback(null, idOrName, objects[idOrName].common.name);
} else {
// Assume it is name
for (let id in objects) {
if (!checkObject(id, options, 4/*read*/)) continue;
if (objects[id].common &&
objects[id].common.name === idOrName &&
(!type || (objects[id].common && objects[id].common.type === type))) {
if (typeof callback === 'function') callback(null, id, idOrName);
return;
}
}
if (typeof callback === 'function') callback(null, null, idOrName);
}
};
// can be called only from js-controller
this.addPreserveSettings = function (settings) {
if (typeof settings !== 'object') settings = [settings];
for (let s = 0; s < settings.length; s++) {
if (preserveSettings.indexOf(settings[s]) === -1) preserveSettings.push(settings[s]);
}
};
this.destroyDB = function (options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
if (!options.checked) {
checkObjectRights(null, options, 0x2/*write*/, function (err, options) {
if (err) {
if (typeof callback === 'function') callback(err);
} else {
if (!options.acl.file.write) {
if (typeof callback === 'function') callback('permissionError');
} else {
return that.destroyDB(options, callback);
}
}
});
return;
}
if (fs.existsSync(objectsName)) {
fs.unlinkSync(objectsName);
}
if (typeof callback === 'function') callback();
};
function socketEvents(socket /*, user*/) {
socket.on('writeFile', function (id, name, data, options, callback) {
that.writeFile.apply(that, arguments);
});
socket.on('destroy', function (callback) {
// client may not close DB
if (typeof callback === 'function') callback();
//that.destroy.apply(that, arguments);
});
socket.on('enableFileCache', function (enabled, options, callback) {
that.enableFileCache.apply(that, arguments);
});
socket.on('readFile', function (id, name, params, options, callback) {
that.readFile.apply(that, arguments);
});
socket.on('readDir', function (id, path, options, callback) {
that.readDir.apply(that, arguments);
});
socket.on('unlink', function (id, name, options, callback) {
that.unlink.apply(that, arguments);
});
socket.on('rename', function (id, oldName, newName, options, callback) {
that.rename.apply(that, arguments);
});
socket.on('mkdir', function (id, dirname, callback) {
that.mkdir.apply(that, arguments);
});
socket.on('chownFile', function (id, path, options, callback) {
that.chownFile.apply(that, arguments);
});
socket.on('chmodFile', function (id, path, options, callback) {
that.chmodFile.apply(that, arguments);
});
socket.on('rm', function (id, path, options, callback) {
that.rm.apply(that, arguments);
});
socket.on('touch', function (id, path, options, callback) {
that.touch.apply(that, arguments);
});
socket.on('subscribe', function (pattern, options) {
// it must be "this" and not "that"
that.subscribe.apply(this, arguments);
});
socket.on('unsubscribe', function (pattern, options) {
// it must be "this" and not "that"
that.unsubscribe.apply(this, arguments);
});
socket.on('getObjectView', function (design, search, params, options, callback) {
that.getObjectView.apply(that, arguments);
});
socket.on('getObjectList', function (params, options, callback) {
that.getObjectList.apply(that, arguments);
});
socket.on('extendObject', function (id, obj, options, callback) {
that.extendObject.apply(that, arguments);
});
socket.on('setObject', function (id, obj, options, callback) {
that.setObject.apply(that, arguments);
});
socket.on('delObject', function (id, options, callback) {
that.delObject.apply(that, arguments);
});
socket.on('findObject', function (idOrName, type, options, callback) {
that.findObject.apply(that, arguments);
});
socket.on('destroyDB', function (options, callback) {
that.destroyDB.apply(that, arguments);
});
socket.on('getObject', function (id, options, callback) {
that.getObject.apply(that, arguments);
});
socket.on('chownObject', function (pattern, options, callback) {
that.chownObject.apply(that, arguments);
});
socket.on('chmodObject', function (pattern, options, callback) {
that.chmodObject.apply(that, arguments);
});
socket.on('error', function (err) {
log.error(namespace + ' ' + err);
});
}
function initSocket(socket) {
if (settings.auth) {
let user = null;
socketEvents(socket /*, user*/);
} else {
socketEvents(socket);
}
}
function _initWebServer(settings, server) {
try {
if (settings.secure) {
if (!settings.certificates) return;
server.server = require('https').createServer(settings.certificates, function (req, res) {
res.writeHead(501);
res.end('Not Implemented');
});
} else {
server.server = require('http').createServer(function (req, res) {
res.writeHead(501);
res.end('Not Implemented');
});
}
server.server.listen(settings.port || 9001, (settings.host && settings.host !== 'localhost') ? settings.host : ((settings.host === 'localhost') ? '127.0.0.1' : undefined));
} catch (e) {
log.error(namespace + ' Cannot start inMem-objects on port ' + (settings.port || 9001) + ': ' + e.message);
console.log('Cannot start inMem-objects on port ' + (settings.port || 9001) + ': ' + e.message);
process.exit(24);
}
server.io = socketio.listen(server.server);
if (settings.auth) {
server.io.use(function (socket, next) {
if (!socket.request._query.user || !socket.request._query.pass) {
console.log('No password or username!');
next(new Error('Authentication error'));
} else {
next(new Error('Authentication error'));
// TODO
/*adapter.checkPassword(socket.request._query.user, socket.request._query.pass, function (res) {
if (res) {
console.log("Logged in: " + socket.request._query.user + ', ' + socket.request._query.pass);
return next();
} else {
console.log("Invalid password or user name: " + socket.request._query.user + ', ' + socket.request._query.pass);
next(new Error('Invalid password or user name'));
}
});*/
}
});
}
server.io.set('origins', '*:*');
server.io.on('connection', initSocket);
log.info(namespace + ' ' + (settings.secure ? 'Secure ' : '') + ' inMem-objects listening on port ' + (settings.port || 9001));
}
// Destructor of the class. Called by shutting down.
this.destroy = function () {
if (configTimer) saveConfig();
saveFileSettings(true);
if (server.io) {
if (server.io.sockets && server.io.sockets.connected) {
for (let s in server.io.sockets.connected) {
if (server.io.sockets.connected.hasOwnProperty(s)) {
delete server.io.sockets.connected[s];
}
}
}
try {
server.io.close();
} catch (e) {
console.log(e.message);
}
}
};
let __construct = (function () {
if (fs.existsSync(objectsName)) {
try {
objects = JSON.parse(fs.readFileSync(objectsName).toString());
} catch (e) {
log.error(namespace + ' Cannot parse ' + objectsName + ': ' + e);
if (fs.existsSync(objectsName + '.bak')) {
try {
objects = JSON.parse(fs.readFileSync(objectsName + '.bak').toString());
} catch (e) {
log.error(namespace + ' Cannot parse ' + objectsName + '.bak: ' + e);
objects = {};
}
} else {
objects = {};
}
}
} else if (fs.existsSync(objectsName + '.bak')) {
try {
objects = JSON.parse(fs.readFileSync(objectsName + '.bak').toString());
} catch (e) {
log.error(namespace + ' Cannot parse ' + objectsName + '.bak: ' + e);
objects = {};
}
} else {
objects = {};
}
// init default new acl
if (objects['system.config'] && objects['system.config'].common && objects['system.config'].common.defaultNewAcl) {
defaultNewAcl = Object.assign({}, objects['system.config'].common.defaultNewAcl);
}
// Create history directory
// (!fs.existsSync(historyName)) fs.mkdirSync(historyName);
change = settings.change || function (id /*, obj */) {
log.silly(namespace + ' objects change: ' + id + ' ' + JSON.stringify(change));
};
// Check if directory exists
objectsName = objectsName.replace(/\\/g, '/');
/** @type {string|string[]} */
let parts = objectsName.split('/');
parts.pop();
parts = parts.join('/');
if (!fs.existsSync(parts)) fs.mkdirSync(parts);
_initWebServer(settings.connection, server);
if (settings.connected) {
setImmediate(function () {
settings.connected('InMemoryDB');
});
}
})();
}
module.exports = ObjectsInMemServer;