Is 7zip stdout broken? Is there a way to capture the progress in nodejs? [Windows] - node.js

I am trying to get the stdout of 7zip when it processes files and get the percentage in nodeJs, but it doesn't behave as expected. 7zip doesn't output anything to stdout until the very end of the execution. Which is not very helpful.. especially when I have large files being compressed and no feedback is shown for a very long time.
The code I am using (simplified):
// 7zip test, place the 7z.exe in the same dir, if it's not on %PATH%
var cp = require('child_process');
var inputFile = process.argv[2]; if(inputFile==null) return;
var regProgress = /(\d{1,3})%\s*$/; //get the last percentage of the string, 3 digits
var proc = cp.spawn("7z.exe",["a","-t7z" ,"-y" ,inputFile + ".7z",inputFile]);
proc.stdout.setEncoding("utf8");
proc.stdout.on("data",function(data){
if(regProgress.test(data))
console.log("Progress = " + regProgress.exec(data)[1] + "%");
});
proc.once("exit",function(exit,sig){ console.log("Complete"); });
I have used the same code to get the percentage with WinRar successfully and I am beginning to think that 7zip might be buggy? Or I am doing it wrong? Can I forcefully read the stdout of a process with a timer perhaps?
The same code above, with the exception of the following line replaced, works as expected with WinRar.
var proc = cp.spawn("Rar.exe",["a","-s","-ma5","-o+",inputFile+".rar",inputFile]);
If anyone knows why this happens and if it is fixable, I would be grateful! :-)
p.s. I have tried 7za.exe, the command line version of 7zip, also the stable, beta and alpha versions, they all have the same issue

It is no longer needed to use a terminal emulator like pty.js, you can pass the -bsp1 to 7z to force to output the progress to stdout.

7-zip only outputs progress when stdout is a terminal.
To trick 7-zip, you need to npm install pty.js (requires Visual Studio or VS Express with Windows SDK) and then use code like:
var pty = require('pty');
var inputFile = process.argv[2],
pathTo7zip = 'c:\\Program Files\\7-Zip\\7z.exe';
if (inputFile == null)
return;
var term = pty.spawn(process.env.ComSpec, [], {
name: 'ansi',
cols: 200,
rows: 30,
cwd: process.env.HOME,
env: process.env
});
var rePrg = /(\d{1,3})%\r\n?/g,
reEsc = /\u001b\[\w{2}/g,
reCwd = new RegExp('^' + process.cwd().replace(/\\/g, '\\\\'), 'm');
prompts = 0,
buffer = '';
term.on('data', function(data) {
var m, idx;
buffer += data;
// remove terminal escape sequences
buffer = buffer.replace(reEsc, '');
// check for multiple progress indicators in the current buffer
while (m = rePrg.exec(buffer)) {
idx = m.index + m[0].length;
console.log(m[1] + ' percent done!');
}
// check for the cmd.exe prompt
if (m = reCwd.exec(buffer)) {
if (++prompts === 2) {
// command is done
return term.kill();
} else {
// first prompt is before we started the actual 7-zip process
if (idx === undefined) {
// we didn't see a progress indicator, so make sure to truncate the
// prompt from our buffer so that we don't accidentally detect the same
// prompt twice
buffer = buffer.substring(m.index + m[0].length);
return;
}
}
}
// truncate the part of our buffer that we're done processing
if (idx !== undefined)
buffer = buffer.substring(idx);
});
term.write('"'
+ pathTo7zip
+ '" a -t7z -y "'
+ inputFile
+ '.7z" "'
+ inputFile
+ '"\r');
It should be noted that 7-zip does not always output 100% at finish. If the file compresses quickly, you may just see only a single 57% for example, so you will have to handle that however you want.

Related

How to completely erase the console output in windows?

I'm trying to erase the windows console programmatically from a NodeJS script. Not just slide the console output out of view...I want to actually clear it.
I'm writing a tool similar to TypeScript's tsc command, where it will watch a folder and incrementally compile the project. As such, on every file change, I rerun the compiler, and output any errors that are found (one line each). I would like to totally erase the console output so that users are not confused by old error messages as they scroll up the console.
When you run tsc --watch in a directory, TypeScript does exactly what I want. tsc actually erases the entire console output.
I've tried all of the following things:
process.stdout.write("\x1Bc");
process.stdout.write('\033c')
var clear = require('cli-clear'); clear();
I tried all of the escape codes from this post.
process.stdout.write("\u001b[2J\u001b[0;0H");
All of these either:
Printed an unknown char to the console
Slid the console down, equivalent to cls, which is NOT what I want.
How do I actually clear the screen and remove ALL of the output? I'm open to using a node module, piping outupt, spawning new cmds, hacks, etc, as long as it gets the job done.
Here's a sample node.js script to test out the issue.
for (var i = 0; i < 15; i++) {
console.log(i + ' --- ' + i);
}
//clear the console output here somehow
Adapted from a previous answer. You will need a C compiler (tested with mingw/gcc)
#include <windows.h>
int main(void){
HANDLE hStdout;
CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
COORD destinationPoint;
SMALL_RECT sourceArea;
CHAR_INFO Fill;
// Get console handle
hStdout = CreateFile( "CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0 );
// Retrieve console information
if (GetConsoleScreenBufferInfo(hStdout, &csbiInfo)) {
// Select all the console buffer as source
sourceArea.Top = 0;
sourceArea.Left = 0;
sourceArea.Bottom = csbiInfo.dwSize.Y - 1;
sourceArea.Right = csbiInfo.dwSize.X - 1;
// Select a place out of the console to move the buffer
destinationPoint.X = 0;
destinationPoint.Y = 0 - csbiInfo.dwSize.Y;
// Configure fill character and attributes
Fill.Char.AsciiChar = ' ';
Fill.Attributes = csbiInfo.wAttributes;
// Move all the information out of the console buffer and init the buffer
ScrollConsoleScreenBuffer( hStdout, &sourceArea, NULL, destinationPoint, &Fill);
// Position the cursor
destinationPoint.X = 0;
destinationPoint.Y = 0;
SetConsoleCursorPosition( hStdout, destinationPoint );
}
return 0;
}
Compiled as clearConsole.exe (or whatever you want), it can be used from node as
const { spawn } = require('child_process');
spawn('clearConsole.exe');

How to disable .save command in Node.js repl?

I'm a newbee to Node.js. I was reading REPL api just now, I assumed setting environment variable NODE_REPL_HISTORY to "" would turn off .save command which produces command lines history file. Was I wrong?
So I decided to set it by process module:
var repl = require("repl");
process.env['NODE_REPL_HISTORY'] = "";
var replServer = repl.start({
prompt:"my-app > ",
});
console.log(process.env);
var add = function(a,b){
return a+b;
};
replServer.context.foo = "bar";
replServer.context.add = add;
Unluckily, REPL still produced command lines history file.
.save command is defined in lib/repl.js unconditionally, that is it's present regardless of any environment variables.
repl.defineCommand('save', {
help: 'Save all evaluated commands in this REPL session to a file',
action: function(file) {
try {
fs.writeFileSync(file, this.lines.join('\n') + '\n');
this.outputStream.write('Session saved to:' + file + '\n');
} catch (e) {
this.outputStream.write('Failed to save:' + file + '\n');
}
this.displayPrompt();
}
});
You can delete this command by removing it manually from replServer:
delete replServer.commands.save;

nodejs child_process.spawn msdeploy.exe with space in dest site

I am trying to use child_process.spawn with msdeploy.exe to automate deployement of some applications in IIS.
Whenever i have a space in my dest site name this makes msdeploy crash.
var command = 'C:/Program Files/IIS/Microsoft Web Deploy V3/msdeploy.exe';
var args = [];
args.push('-verb=sync');
args.push('-source:iisApp=C:/Users/PATH_TO_DEPLOY/dist');
args.push('-dest:iisApp=Default Web Site/test');
var process = spawn(command,args);
process.stdout.on('data', function(data) { grunt.log.write(data) });
process.stderr.on('data', function(data) { grunt.log.error(data); });
process.on('exit', function(code) {
if (code !== 0) {
grunt.fail.warn('Something went wrong');
}
done();
});
I've tried some others alternative like put " '-dest:iisApp="Default Web Site/test"' but msdeploy give me an error too.
This error is like : Argument '"-dest:iisApp=Default Web Site/haha"' not recognized. All arguments must begin with "-" char.
When i try to escape the space char or put " like describe above this gave me a similar error.
Is this is a bug in nodejs ? Maybe i've made something wrong ?
Thank.
How to accomplish this:
var path = require('path');
var platform = require('platform');
var cp = require('child_process');
var full_cmd = '/path/to/dir with space/program.exe';
var cmd = '.' + path.sep + path.basename(full_cmd); // cannot include double quotes -- the work-around is to use the 'cmd_opts.cwd'
var cmd_args = ['"--import-path=/path/to/dir with space/import_file"']; // can wrap each with double-quotes (Windows only -- fails on Unix)
var cmd_opts = {
cwd: path.dirname(full_cmd),
encoding: 'utf8'
};
if (platform.os() === 'win32') {
cmd_opts.windowsVerbatimArguments = true;
}
var proc = cp.spawn(
cmd,
cmd_args,
cmd_opts
);
The only way this doesn't work is if 'program.exe' is named something like 'program name with space.exe'

fs.watch unexpected behavior

If I run the below program as node watcher.js file.txt, then it works as expected when I touch file.txt. But if I open file.txt in vim and save, then it ceases to detect future modifications to the file. This seems really weird to me, why does this behavior occur?
var fs = require('fs');
var args = process.argv;
if (args.length <= 2) {
console.log('USAGE: ' + args[1] + ' filename');
process.exit(1);
}
var filename = args[2];
fs.watch(filename, function(event, filename) {
console.log('file ' + filename + ' changed!');
});
It is important to inspect the content of the first argument, not just the filename. The issue is that event can be either 'change' OR 'rename'.
In this case, it looks like vim is actually renaming the old file and making a new one.

Meteor/Node writeFile crashes server

I have the following code:
Meteor.methods({
saveFile: function(blob, name, path, encoding) {
var path = cleanPath(path), fs = __meteor_bootstrap__.require('fs'),
name = cleanName(name || 'file'), encoding = encoding || 'binary',
chroot = Meteor.chroot || 'public';
// Clean up the path. Remove any initial and final '/' -we prefix them-,
// any sort of attempt to go to the parent directory '..' and any empty directories in
// between '/////' - which may happen after removing '..'
path = chroot + (path ? '/' + path + '/' : '/');
// TODO Add file existance checks, etc...
fs.writeFile(path + name, blob, encoding, function(err) {
if (err) {
throw (new Meteor.Error(500, 'Failed to save file.', err));
} else {
console.log('The file ' + name + ' (' + encoding + ') was saved to ' + path);
}
});
function cleanPath(str) {
if (str) {
return str.replace(/\.\./g,'').replace(/\/+/g,'').
replace(/^\/+/,'').replace(/\/+$/,'');
}
}
function cleanName(str) {
return str.replace(/\.\./g,'').replace(/\//g,'');
}
}
});
Which I took from this project
https://gist.github.com/dariocravero/3922137
The code works fine, and it saves the file, however it repeats the call several time and each time it causes meteor to reset using windows version 0.5.4. The F12 console ends up looking like this: . The meteor console loops over the startup code each time the 503 happens and repeats the console logs in the saveFile function.
Furthermore in the target directory the image thumbnail keeps displaying and then display as broken, then a valid thumbnail again, as if the fs is writing it multiple times.
Here is the code that calls the function:
"click .savePhoto":function(e, template){
e.preventDefault();
var MAX_WIDTH = 400;
var MAX_HEIGHT = 300;
var id = e.srcElement.id;
var item = Session.get("employeeItem");
var file = template.find('input[name='+id+']').files[0];
// $(template).append("Loading...");
var dataURL = '/.bgimages/'+file.name;
Meteor.saveFile(file, file.name, "/.bgimages/", function(){
if(id=="goodPhoto"){
EmployeeCollection.update(item._id, { $set: { good_photo: dataURL }});
}else{
EmployeeCollection.update(item._id, { $set: { bad_photo: dataURL }});
}
// Update an image on the page with the data
$(template.find('img.'+id)).delay(1000).attr('src', dataURL);
});
},
What's causing the server to reset?
My guess would be that since Meteor has a built-in "automatic directories scanning in search for file changes", in order to implement auto relaunching of the application to newest code-base, the file you are creating is actually causing the server reset.
Meteor doesn't scan directories beginning with a dot (so called "hidden" directories) such as .git for example, so you could use this behaviour to your advantage by setting the path of your files to a .directory of your own.
You should also consider using writeFileSync insofar as Meteor methods are intended to run synchronously (inside node fibers) contrary to the usual node way of asynchronous calls, in this code it's no big deal but for example you couldn't use any Meteor mechanics inside the writeFile callback.
asynchronousCall(function(error,result){
if(error){
// handle error
}
else{
// do something with result
Collection.update(id,result);// error ! Meteor code must run inside fiber
}
});
var result=synchronousCall();
Collection.update(id,result);// good to go !
Of course there is a way to turn any asynchronous call inside a synchronous one using fibers/future, but that's beyond the point of this question : I recommend reading this EventedMind episode on node future to understand this specific area.

Resources