mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
The tests still use it in places - particular localfilesystem tests, but those tests need to be redone with sinon stubbing in place and not rely on real fs operations.
364 lines
12 KiB
JavaScript
364 lines
12 KiB
JavaScript
/**
|
|
* Copyright 2013, 2014 IBM Corp.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
**/
|
|
|
|
var fs = require('fs-extra');
|
|
var when = require('when');
|
|
var nodeFn = require('when/node/function');
|
|
var keys = require('when/keys');
|
|
var fspath = require("path");
|
|
var mkdirp = fs.mkdirs;
|
|
|
|
var log = require("../log");
|
|
|
|
var promiseDir = nodeFn.lift(mkdirp);
|
|
|
|
var settings;
|
|
var flowsFile;
|
|
var flowsFullPath;
|
|
var flowsFileBackup;
|
|
var credentialsFile;
|
|
var credentialsFileBackup;
|
|
var oldCredentialsFile;
|
|
var sessionsFile;
|
|
var libDir;
|
|
var libFlowsDir;
|
|
var globalSettingsFile;
|
|
|
|
function getFileMeta(root,path) {
|
|
var fn = fspath.join(root,path);
|
|
var fd = fs.openSync(fn,"r");
|
|
var size = fs.fstatSync(fd).size;
|
|
var meta = {};
|
|
var read = 0;
|
|
var length = 10;
|
|
var remaining = "";
|
|
var buffer = Buffer(length);
|
|
while(read < size) {
|
|
read+=fs.readSync(fd,buffer,0,length);
|
|
var data = remaining+buffer.toString();
|
|
var parts = data.split("\n");
|
|
remaining = parts.splice(-1);
|
|
for (var i=0;i<parts.length;i+=1) {
|
|
var match = /^\/\/ (\w+): (.*)/.exec(parts[i]);
|
|
if (match) {
|
|
meta[match[1]] = match[2];
|
|
} else {
|
|
read = size;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
fs.closeSync(fd);
|
|
return meta;
|
|
}
|
|
|
|
function getFileBody(root,path) {
|
|
var body = "";
|
|
var fn = fspath.join(root,path);
|
|
var fd = fs.openSync(fn,"r");
|
|
var size = fs.fstatSync(fd).size;
|
|
var scanning = true;
|
|
var read = 0;
|
|
var length = 50;
|
|
var remaining = "";
|
|
var buffer = Buffer(length);
|
|
while(read < size) {
|
|
var thisRead = fs.readSync(fd,buffer,0,length);
|
|
read += thisRead;
|
|
if (scanning) {
|
|
var data = remaining+buffer.slice(0,thisRead).toString();
|
|
var parts = data.split("\n");
|
|
remaining = parts.splice(-1)[0];
|
|
for (var i=0;i<parts.length;i+=1) {
|
|
if (! /^\/\/ \w+: /.test(parts[i])) {
|
|
scanning = false;
|
|
body += parts[i]+"\n";
|
|
}
|
|
}
|
|
if (! /^\/\/ \w+: /.test(remaining)) {
|
|
scanning = false;
|
|
}
|
|
if (!scanning) {
|
|
body += remaining;
|
|
}
|
|
} else {
|
|
body += buffer.slice(0,thisRead).toString();
|
|
}
|
|
}
|
|
fs.closeSync(fd);
|
|
return body;
|
|
}
|
|
|
|
/**
|
|
* Write content to a file using UTF8 encoding.
|
|
* This forces a fsync before completing to ensure
|
|
* the write hits disk.
|
|
*/
|
|
function writeFile(path,content) {
|
|
return when.promise(function(resolve,reject) {
|
|
var stream = fs.createWriteStream(path);
|
|
stream.on('open',function(fd) {
|
|
stream.end(content,'utf8',function() {
|
|
fs.fsync(fd,resolve);
|
|
});
|
|
});
|
|
stream.on('error',function(err) {
|
|
reject(err);
|
|
});
|
|
});
|
|
}
|
|
|
|
var localfilesystem = {
|
|
init: function(_settings) {
|
|
settings = _settings;
|
|
|
|
var promises = [];
|
|
|
|
if (!settings.userDir) {
|
|
try {
|
|
fs.statSync(fspath.join(process.env.NODE_RED_HOME,".config.json"));
|
|
settings.userDir = process.env.NODE_RED_HOME;
|
|
} catch(err) {
|
|
settings.userDir = fspath.join(process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE || process.env.NODE_RED_HOME,".node-red");
|
|
if (!settings.readOnly) {
|
|
promises.push(promiseDir(settings.userDir));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (settings.flowFile) {
|
|
flowsFile = settings.flowFile;
|
|
// handle Unix and Windows "C:\"
|
|
if ((flowsFile[0] == "/") || (flowsFile[1] == ":")) {
|
|
// Absolute path
|
|
flowsFullPath = flowsFile;
|
|
} else if (flowsFile.substring(0,2) === "./") {
|
|
// Relative to cwd
|
|
flowsFullPath = fspath.join(process.cwd(),flowsFile);
|
|
} else {
|
|
try {
|
|
fs.statSync(fspath.join(process.cwd(),flowsFile));
|
|
// Found in cwd
|
|
flowsFullPath = fspath.join(process.cwd(),flowsFile);
|
|
} catch(err) {
|
|
// Use userDir
|
|
flowsFullPath = fspath.join(settings.userDir,flowsFile);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
flowsFile = 'flows_'+require('os').hostname()+'.json';
|
|
flowsFullPath = fspath.join(settings.userDir,flowsFile);
|
|
}
|
|
var ffExt = fspath.extname(flowsFullPath);
|
|
var ffName = fspath.basename(flowsFullPath);
|
|
var ffBase = fspath.basename(flowsFullPath,ffExt);
|
|
var ffDir = fspath.dirname(flowsFullPath);
|
|
|
|
credentialsFile = fspath.join(settings.userDir,ffBase+"_cred"+ffExt);
|
|
credentialsFileBackup = fspath.join(settings.userDir,"."+ffBase+"_cred"+ffExt+".backup");
|
|
|
|
oldCredentialsFile = fspath.join(settings.userDir,"credentials.json");
|
|
|
|
flowsFileBackup = fspath.join(ffDir,"."+ffName+".backup");
|
|
|
|
sessionsFile = fspath.join(settings.userDir,".sessions.json");
|
|
|
|
libDir = fspath.join(settings.userDir,"lib");
|
|
libFlowsDir = fspath.join(libDir,"flows");
|
|
|
|
globalSettingsFile = fspath.join(settings.userDir,".config.json");
|
|
|
|
if (!settings.readOnly) {
|
|
promises.push(promiseDir(libFlowsDir));
|
|
}
|
|
|
|
return when.all(promises);
|
|
},
|
|
|
|
getFlows: function() {
|
|
return when.promise(function(resolve) {
|
|
log.info(log._("storage.localfilesystem.user-dir",{path:settings.userDir}));
|
|
log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath}));
|
|
fs.readFile(flowsFullPath,'utf8',function(err,data) {
|
|
if (!err) {
|
|
return resolve(JSON.parse(data));
|
|
}
|
|
log.info(log._("storage.localfilesystem.create"));
|
|
resolve([]);
|
|
});
|
|
});
|
|
},
|
|
|
|
saveFlows: function(flows) {
|
|
if (settings.readOnly) {
|
|
return when.resolve();
|
|
}
|
|
|
|
try {
|
|
fs.renameSync(flowsFullPath,flowsFileBackup);
|
|
} catch(err) {
|
|
}
|
|
|
|
var flowData;
|
|
|
|
if (settings.flowFilePretty) {
|
|
flowData = JSON.stringify(flows,null,4);
|
|
} else {
|
|
flowData = JSON.stringify(flows);
|
|
}
|
|
return writeFile(flowsFullPath, flowData);
|
|
},
|
|
|
|
getCredentials: function() {
|
|
return when.promise(function(resolve) {
|
|
fs.readFile(credentialsFile,'utf8',function(err,data) {
|
|
if (!err) {
|
|
resolve(JSON.parse(data));
|
|
} else {
|
|
fs.readFile(oldCredentialsFile,'utf8',function(err,data) {
|
|
if (!err) {
|
|
resolve(JSON.parse(data));
|
|
} else {
|
|
resolve({});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
saveCredentials: function(credentials) {
|
|
if (settings.readOnly) {
|
|
return when.resolve();
|
|
}
|
|
|
|
try {
|
|
fs.renameSync(credentialsFile,credentialsFileBackup);
|
|
} catch(err) {
|
|
}
|
|
var credentialData;
|
|
if (settings.flowFilePretty) {
|
|
credentialData = JSON.stringify(credentials,null,4);
|
|
} else {
|
|
credentialData = JSON.stringify(credentials);
|
|
}
|
|
return writeFile(credentialsFile, credentialData);
|
|
},
|
|
|
|
getSettings: function() {
|
|
return when.promise(function(resolve,reject) {
|
|
fs.readFile(globalSettingsFile,'utf8',function(err,data) {
|
|
if (!err) {
|
|
try {
|
|
return resolve(JSON.parse(data));
|
|
} catch(err2) {
|
|
log.trace("Corrupted config detected - resetting");
|
|
}
|
|
}
|
|
return resolve({});
|
|
})
|
|
})
|
|
},
|
|
saveSettings: function(settings) {
|
|
if (settings.readOnly) {
|
|
return when.resolve();
|
|
}
|
|
return writeFile(globalSettingsFile,JSON.stringify(settings,null,1));
|
|
},
|
|
getSessions: function() {
|
|
return when.promise(function(resolve,reject) {
|
|
fs.readFile(sessionsFile,'utf8',function(err,data){
|
|
if (!err) {
|
|
try {
|
|
return resolve(JSON.parse(data));
|
|
} catch(err2) {
|
|
log.trace("Corrupted sessions file - resetting");
|
|
}
|
|
}
|
|
resolve({});
|
|
})
|
|
});
|
|
},
|
|
saveSessions: function(sessions) {
|
|
if (settings.readOnly) {
|
|
return when.resolve();
|
|
}
|
|
return writeFile(sessionsFile,JSON.stringify(sessions));
|
|
},
|
|
|
|
getLibraryEntry: function(type,path) {
|
|
var root = fspath.join(libDir,type);
|
|
var rootPath = fspath.join(libDir,type,path);
|
|
return promiseDir(root).then(function () {
|
|
return nodeFn.call(fs.lstat, rootPath).then(function(stats) {
|
|
if (stats.isFile()) {
|
|
return getFileBody(root,path);
|
|
}
|
|
if (path.substr(-1) == '/') {
|
|
path = path.substr(0,path.length-1);
|
|
}
|
|
return nodeFn.call(fs.readdir, rootPath).then(function(fns) {
|
|
var dirs = [];
|
|
var files = [];
|
|
fns.sort().filter(function(fn) {
|
|
var fullPath = fspath.join(path,fn);
|
|
var absoluteFullPath = fspath.join(root,fullPath);
|
|
if (fn[0] != ".") {
|
|
var stats = fs.lstatSync(absoluteFullPath);
|
|
if (stats.isDirectory()) {
|
|
dirs.push(fn);
|
|
} else {
|
|
var meta = getFileMeta(root,fullPath);
|
|
meta.fn = fn;
|
|
files.push(meta);
|
|
}
|
|
}
|
|
});
|
|
return dirs.concat(files);
|
|
});
|
|
}).otherwise(function(err) {
|
|
if (type === "flows" && !/\.json$/.test(path)) {
|
|
return localfilesystem.getLibraryEntry(type,path+".json")
|
|
.otherwise(function(e) {
|
|
throw err;
|
|
});
|
|
} else {
|
|
throw err;
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
saveLibraryEntry: function(type,path,meta,body) {
|
|
if (settings.readOnly) {
|
|
return when.resolve();
|
|
}
|
|
var fn = fspath.join(libDir, type, path);
|
|
var headers = "";
|
|
for (var i in meta) {
|
|
if (meta.hasOwnProperty(i)) {
|
|
headers += "// "+i+": "+meta[i]+"\n";
|
|
}
|
|
}
|
|
return promiseDir(fspath.dirname(fn)).then(function () {
|
|
writeFile(fn,headers+body);
|
|
});
|
|
}
|
|
};
|
|
|
|
module.exports = localfilesystem;
|