Is it possible to overwrite a file with nodejs? - node.js

I am developing SFTP server with nodejs and I am writing a file which contain some code errors.
Here's my code :
readfile(path, writestream, fileError) {
if (path === '/put.error') {
if (fileError) {
console.warn("Dowloading : " + path);
var data = JSON.stringify(fileError, null, 4);
writestream.write(data);
console.warn("File downloaded")
}
writestream.end();
return true;
}
writestream.end();
return true;
}
It works fine the first time I did "get put.error" but I got an issue when I wanted to do download it again because the file already exist. How I could overwrite this file ?
I don't need to keep old datas, just to destroy it or overwrite.

Related

NodeJS - If moving multiple files fails, deleting ones that failed

I'm trying to upload multiple files with an HTTP post, and then NodeJS handles:
save files' info to database
move files from tmp folder to permanent folder
if any file move fails, delete the file from tmp folder
My two issues are described in the comments within code snippet below:
path.resolve isn't working
iterator isn't working within fs.rename
for (i = 0; i < req.files.length; i++) {
const file = new fileSchema({
_userId: req.body._userId,
_companyId: req.body._companyId,
_meetingId: response._id,
originalFilename: req.files[i].originalname,
savedFilename: req.files[i].filename,
});
file.save().then((response) => { }).catch(error => { console.log(error) });
const currentPath = path.resolve(temp_folder, req.files[i].filename);
const newPath = upload_folder +"/"+ req.body._userId +"/"+ req.body._companyId +"/"+ response._id +"/"+ req.files[i].filename;
// 1. why doesn't path.resolve work with the inputs on the line above? I have to concat a string as in line above?
fs.rename(currentPath, newPath, function(err) {
if (err) {
console.log("Error moving files");
try { removeTempFiles(temp_folder, req.files[i]); } // helper function which works written elsewhere
// 2. req.files[i] is undefined (even though req.files works) so the line above fails - i.e. the iterator isn't captured within rename?
catch(err) { console.log(err); }
} else {
console.log("Successfully moved the file!");
}
});
}
Any help appreciated, thanks.
Change this
for (i = 0; i < req.files.length; i++) {
to this:
for (let i = 0; i < req.files.length; i++) {
The addition of let will create a separate i for each iteration of the for loop so it will stay valid inside your fs.rename() callback.
And, path.join(), is probably a better choice than path.resolve() for combining path segments.

Electron JS write file permission problems

We are developing an Electron JS app which should get a configuration file from a server at one part. This worked until an hour ago, but now it "magically" throws a permission error. It throws a permission error when we try to write to anything. Here is what we explicitly tested:
app.getPath('userData')
"C:/test"
app.getAppPath()
We tried lauching it from an administrator elevated powershell, but still no success. This is our code snippet:
function canWrite(path, callback) {
fs.access(path, fs.W_OK, function (err) {
callback(null, !err);
});
}
function downloadFile(url, target, target_name) {
canWrite(target, function (err, isWritable) {
if (isWritable){
electronDl.download(
BrowserWindow.getFocusedWindow(),
url,
{
directory: target,
filename: target_name
}
)
console.log("Downloaded from: " + url + " to: " + target);
return true;
} else {
console.log("No permission to write to target");
return false;
}
});
}
downloadFile(REMOTEURL, app.getPath('userData'), 'sessionfile.json');
We rewrote this code, tried to change filenames, tried it without the filename (..) and are a bit out of ideas now. We furthermore implemented a file check (whether the file exists or not) and if so a deletion before executing this. We commented it out for now for debugging because it worked before.
Update:
After somebody pointed out that the outer check is pretty useless, I updated the code to this (still doesn't work):
function downloadFile(url, target) {
electronDl.download(
BrowserWindow.getFocusedWindow(),
url,
{
directory: target,
}
)
}
downloadFile(REMOTEURL, "C:/test");
Since it appears that electron-dl doesn't give clear error messages, you may want to check/create the directory beforehand as you initially did.
The basic procedure could look like this:
Check if the target directory exists.
If it exists, check if it is writable.
If it is writable, proceed to downloading.
If it is not writable, print an informative error message and stop.
If it doesn't exist, try to create it.
If this works, proceed to downloading.
If this fails, print an informative error message and stop.
The following code implements this idea (using the synchronous versions of the fs methods for simplicity). Be sure to use the asynchronous versions if required.
const electronDl = require('electron-dl')
const fs = require('fs')
function ensureDirExistsAndWritable(dir) {
if (fs.existsSync(dir)) {
try {
fs.accessSync(dir, fs.constants.W_OK)
} catch (e) {
console.error('Cannot access directory')
return false
}
}
else {
try {
fs.mkdirSync(dir)
}
catch (e) {
if (e.code == 'EACCES') {
console.log('Cannot create directory')
}
else {
console.log(e.code)
}
return false
}
}
return true
}
function downloadFile(url, target) {
if (ensureDirExistsAndWritable(target) == false) {
return
}
electronDl.download(
BrowserWindow.getFocusedWindow(),
url,
{
directory: target,
}
)
.then(
dl => console.log('Successfully downloaded to ' + dl.getSavePath())
)
.catch(
console.log('There was an error downloading the file')
)
}

A way to make onDeterminingFileName and NativeMessaging work together

I'm trying to determine if a file is already in my system before I download it.
I get the file name from the server through
chrome.downloads.onDeterminingFilename.addListener(function (item, suggest) {
var message = { "fileName": item.filename, "directory": directory };
suggest({
filename: item.filename,
conflict_action: 'prompt',
conflictAction: 'prompt'
});
// conflict_action was renamed to conflictAction in
// https://chromium.googlesource.com/chromium/src/+/f1d784d6938b8fe8e0d257e41b26341992c2552c
// which was first picked up in branch 1580.
});
which works great.
I am checking the filename through native messaging.
chrome.runtime.sendNativeMessage("testcsharp", message, function (response) {
if (chrome.runtime.lastError) {
alert("ERROR: " + chrome.runtime.lastError.message);
} else {
isDownloaded = JSON.parse(JSON.stringify(response)).data;
}
});
The problem is they are both asynchronous but I need to avoid the save as dialog if my file already exists and the only way I've found to do this is call chrome.downloads.cancel(item.id) but there is no guarantee that a response will come in time from native messaging to cancel in ondeterminingfilename. Is there a way to do this or am I just stuck notifying that this file already exists and closing the save dialog manually?

Electron 4 Windows -> electron-builder -> auto-update: custom solution

I'm building an app for windows using Electron. To package and distribute it I'm using electron-builder. Electron-builder relies on many packages, and for auto-updates it uses Squirrel-windows.
I've been battling with auto-update on Windows for 3 days and at the end I've come up with a working solution that seems to give no problems.
I wont' go into the details of what I've tried, and failed. Instead, I'll post here the solution with which I've come up.
I'm sharing it with you guys, to see if you may point out to me any flaws that will make my system fail, or, if it truly is a solid solution, to help those who are struggling as I was. For this latter reason, I'm posting some more code than it would be necessary, hoping it will help others.
The logic is as follows:
if the sub-folder fullupdate inside the path of the current executable does not exists (see later, it will be clarified), we connect with an online server and check if there is an update by sending the current app version;
if there is no update, do nothing.
if there is an update, we instruct the server to return a json string that contains the url from which we can download of the .exe installer produced by electron-builder. NB: not the .nupkg (server code not provided :-)).
we download the file and save it inside a sub folder fullupdate in the local folder in which our app is currently saved. This should be "safe" as electron-builder saves the app in the current user folder AppData, so we should not have permissions issues.
at the end of the download, we create a new file update inside the folder fullupdate to be sure the download has finished successfully. We could also rename the file, but I prefer this way.
next time the app opens:
if the folder fullupdate exists we check if the file update exists. If it does not exists, the download was not finished, so we delete the folder fullupdate and call the remote server again to start all over again.
else, if the file update exists, we launch the .exe file we have downloaded, and return true. This will prevent the app from opening the main window. The cool thing is that the updater will delete the whole old version of the app saved in AppData (while leaving local user data) and replace it with the new version. In this way we will get rid also of the folder fullupdate.
Now the code:
// we want to run this only on windows
var handleStartupEvent = function() {
if (process.platform !== 'win32') {
return false;
}
/////////////////
// MANUAL UPDATER
/////////////////
var appFolder = 'app-' + appVersion;
var pathApp = path.dirname(process.execPath);
var pathUpdate = pathApp + '\\fullupdate';
var checkupdateurl = 'https://api.mysite.com/getjson/' + appVersion.split('.').join('-');
function checkIfDownloaded(){
if (!fs.existsSync(pathUpdate)) checkUpdate();
else return checkIfInstallLocal();
}
function checkIfInstallLocal(){
if(fileExists('fullupdate\\update')) return installLocal();
else {
deleteFolderRecursive(pathUpdate);
checkUpdate();
}
}
function installLocal(){
cp.exec('fullupdate\\Update.exe', function( error, stdout, stderr){
if ( error != null ) {
console.log(stderr);
}
});
return true;
}
// from http://www.geedew.com/remove-a-directory-that-is-not-empty-in-nodejs/
var deleteFolderRecursive = function(path) {
if( fs.existsSync(path) ) {
fs.readdirSync(path).forEach(function(file,index){
var curPath = path + "/" + file;
if(fs.lstatSync(curPath).isDirectory()) deleteFolderRecursive(curPath);
else fs.unlinkSync(curPath);
});
fs.rmdirSync(path);
}
};
// from http://stackoverflow.com/questions/4482686/check-synchronously-if-file-directory-exists-in-node-js
function fileExists(path) {
try {
return fs.statSync(path).isFile();
}
catch (e) {
if (e.code == 'ENOENT') { // no such file or directory. File really does not exist
return false;
}
throw e; // something else went wrong, we don't have rights, ...
}
}
function checkUpdate(){
https.get('https://api.mysite.com/getjson/' + app.getVersion().split('.').join('-'), (res) => {
res.setEncoding('utf8');
res.on('data', function(chunk) {
if(chunk) thereIsUpdate(chunk);
});
}).on('error', (e) => {
console.log(e);
});
}
function thereIsUpdate(chunk){
var data = JSON.parse(chunk);
if(data && data.url) getNewUpdate(data.urlsetup);
}
function getNewUpdate(url){
fs.mkdirSync(pathUpdate);
var file = fs.createWriteStream(pathUpdate + '/Update.exe');
var responseSent = false; // flag to make sure that response is sent only once.
var request = https.get(url, function(response) {
response.pipe(file);
file.on('finish', () =>{
file.close(() => {
if(responseSent) return;
responseSent = true;
});
fs.closeSync(fs.openSync(pathUpdate + '/update', 'w'));
});
});
}
if(checkIfDownloaded()) return true;
/////////////////////////
// SQUIRREL EVENTS HANDLER
//////////////////////////
// see http://stackoverflow.com/questions/30105150/handle-squirrels-event-on-an-electron-app
};
// here we call the function. It is before the opening of the window, so that we prevent the opening if we are updating, or if there is a Squirrel event going on (see SO question, link above)
if (handleStartupEvent()) {
return;
}

MongoDB GridFS - Is it filename or fileName

Please look at the following image, from http://mongoexplorer.com/:
I've been trying to work through GridFS, referencing https://github.com/jamescarr/nodejs-mongodb-streaming. The files I uploaded, come back nicely and the stream that comes back via the following get function looks right.
var gridfs = (function () {
function gridfs() { }
gridfs.get = function (id, fn) {
var db, store;
db = mongoose.connection.db;
id = new ObjectID(id);
store = new GridStore(db, id, "r", {
root: "fs"
});
return store.open(function (err, store) {
if (err) {
return fn(err);
}
return fn(null, store);
});
};
return gridfs;
})();
Using http://mongoexplorer.com/ I uploaded files into GridFS to test with, but they seem broken when I use the node code above to retrieve them.
That is when I noticed the filename / fileName thing. Looking here /node_modules/mongodb/lib/mongodb/gridfs/gridstore.js I saw the reference to filename with a lowercase 'N', but in my GridFS, it's fileName with a capital 'N'.
OK, so just for kicks, I changed it to lowercase in GridFS, but I still get some corruption in the stream (node code above) when retrieving files uploaded with http://mongoexplorer.com/.
Clicking Save as... in http://mongoexplorer.com/, however brings back my fine just perfectly.
To get back to my question, (since my tests didn't seem to prove anything,) I am wondering which is it: filename with a lowercase 'N', or fileName with 'N' in caps?
Please use the latest mongodb native driver as there are a ton of fixes for GridFS, there is a ton of examples in the github directory for the driver under the tests for usage of GridFS as streams as well.
Docs are at
http://mongodb.github.com/node-mongodb-native
In general I would say that if you use the core functionalities stick to the driver as the one you are using it using a driver that' way of of date which explains you corruption issues.
Another Windows tool nl. MongoVue also looks for filename instead of fileName. I'd say the answer is more likely filename instead of fileName.
With retrieving the small Windows file from GridStore, I found a bug, but I don't know how to fix it. I guess there must be some value like Chunk.CurrentSize or the like, but looking at the chunk.js file in the native node mongo driver https://github.com/mongodb/node-mongodb-native/blob/master/lib/mongodb/gridfs/chunk.js, I did the following...
I found this:
Chunk.prototype.readSlice = function(length) {
if ((this.length() - this.internalPosition + 1) >= length) {
var data = null;
if (this.data.buffer != null) { //Pure BSON
data = this.data.buffer.slice(this.internalPosition, this.internalPosition + length);
} else { //Native BSON
data = new Buffer(length);
length = this.data.readInto(data, this.internalPosition);
}
this.internalPosition = this.internalPosition + length;
return data;
} else {
return null;
}
};
and moved the following
data = this.data.buffer.slice(this.internalPosition, this.internalPosition + length);
into the this if statement (1024 * 256 is the value from Chunk.DEFAULT_CHUNK_SIZE = 1024 * 256;)
if (this.data.buffer != null) { //Pure BSON
if (this.data.buffer.length > 1024 * 256) {
// move to here
}
else
{
data = this.data.buffer;
}
like so:
Chunk.prototype.readSlice = function(length) {
if ((this.length() - this.internalPosition + 1) >= length) {
var data = null;
if (this.data.buffer != null) { //Pure BSON
if (this.data.buffer.length > 1024 * 256) {
data = this.data.buffer.slice(this.internalPosition, this.internalPosition + length);
}
else
{
data = this.data.buffer;
}
} else { //Native BSON
data = new Buffer(length);
length = this.data.readInto(data, this.internalPosition);
}
this.internalPosition = this.internalPosition + length;
return data;
} else {
return null;
}
};
The issue with windows files smaller than the chunk size is solved, but this isn't the most elegant solution. I'd like to propose this as the answer, but I realize using the default chunk size hard coded isn't the dynamic value which would make this less of a workaround ;-)

Resources