How to find file extension change with node.js? - node.js

How do you detect a file extension change and save the new extension or full file path to a variable? This is a unique specific question.
Code I have so far dont mind if you dont use it:
const puppeteer = require('C:/Users/user1/Desktop/puppeteer_automation/node_modules/puppeteer');
(async () => {
var filename = "C:/Users/user1/Downloads/file.crdownload";
var downloadanduploadpath = "C:/Users/user1/Downloads";
var fs = require('fs');
var event1 = "change";
var currentstat = fs.stat();
const WATCH_TARGET = filename;
fs.watch(WATCH_TARGET, function(event1, downloadanduploadpath) {
console.log('File "' + filename + '" was changed: ' + eventType);
});
/*
fs.watch(downloadanduploadpath, (event1, filename) => {
console.log('event is: ' + event1);
if (filename) {
console.log('filename provided: ' + filename);
} else {
console.log('filename not provided');
}
});*/
//console.log(lastdownloadedimage);
})();
Error:
(node:9876) UnhandledPromiseRejectionWarning: TypeError
[ERR_INVALID_CALLBACK]: Callback must be a function. Received
undefined

As described in the docs,
The listener callback gets two arguments (eventType, filename). eventType is either 'rename' or 'change', and filename is the name of the file which triggered the event.
you can use the eventType argument to determine whether the filename has been changed.
UPDATE: I updated my example code to observe the directory instead of the file itself, in order to get the new filename in the filename argument
Example:
let fs = require('fs');
(async () => {
const WATCH_DIR = "C:/Users/user1/Downloads";
let target_file = "file.crdownload";
let renameTriggered = false;
fs.watch(WATCH_DIR, function(eventType, filename) {
if(eventType == 'rename') {
// Check if the target filename was changed (in the first event
// the old filename disappears, which marks the beginning of a renaming process)
if(filename == target_file) {
// Toggle renaming status
renameTriggered = true;
}
// The second event captures the new filename, which completes the process
else if(renameTriggered) {
// Toggle renaming status
renameTriggered = false;
console.log('File "' + target_file + '" was renamed to: "' + filename + '"');
// Update target filename
target_file = filename;
}
}
});
})();
Observing the directory however will trigger the callback on any filename changes that occur (includes even deleting and creating files). Therefore we check if the eventType is equal to 'rename'.
From the perspective of fs.watch() the renaming takes place in two steps: In the fisrt step the 'disappearing' of the old filename is detected which triggers the event passing the old filename as the argument filename. In the second step the 'appearance' of the new filename is detected which triggers the rename event again, this time with the new filename as the second argument, which is what we are looking for.

Your code example, is calling fs.watch() wrong. You don't pass arguments to the callback yourself. You just name parameters.
This program below all by itself works on Windows. I pass it a directory and watch for changes to any of the files in that directory. You can also pass it the filename for an existing file and watch for changes to just that file:
const fs = require('fs');
fs.watch("./watcherDir", function(eventType, filename) {
console.log(`eventType=${eventType}, filename=${filename}`);
});
eventType and filename are passed by fs.watch() to the callback, they are not something you pass. You just create the name of the arguments that you can then use as function arguments inside the callback.
When I rename a file in that directory from example.json to example.xxx, I get these two callbacks:
eventType=rename, filename=example.json
eventType=rename, filename=example.xxx
The filenames are, of course, relative to the directory I passed to fs.watch().
Note: As documented, this feature does not work the same on all platforms so for any further debugging we would need to know what platform you're running it on.

Related

How can i catch and handle Error: EEXIST: file already exists, copyfile "somepath" "anotherpath" in nodejs

I want to change the name of file when i get duplicate files while performing copy operation in nodejs using fs module (right now the process exits with error, i want to execute file name change logic on error)
function copyFile(filePath,fileName){
fs.copyFileSync(filePath,
path.join(destination,fileName),fs.constants.COPYFILE_EXCL, (err) => {
if (err) {
fileName= "0"+fileName; //changing the filename
copyFile(filePath,fileName)
}
console.log(fileName+" copied");
})
}
You simply need to check if error.code === 'EEXIST'.
Few notes:
copyFileSync does not accept callback - it's a synchronous function
You're using path.join incorrectly. This utility is used mainly to provide cross-platform paths (Unix - /, Windows - \). If you're concatenating it yourself with / there is no point to use path.join it will not work on non-unix systems anyway.
There is a typo filename -> fileName
You need two fileName... arguments for copyFile function (source and destination), because source file with prepended 0-s not exists by the moment you're calling the function.
const fs = require('fs');
const path = require('path');
const destination = '/tmp/';
function copyFile(filePath, fileNameFrom, fileNameTo=fileNameFrom) {
const from = path.join(filePath, fileNameFrom);
const to = path.join(destination, fileNameTo);
try {
fs.copyFileSync(from, to, fs.constants.COPYFILE_EXCL);
console.log(`${from} copied into ${to}`);
} catch (error) {
console.error(error);
if (error.code === 'EEXIST') {
copyFile(filePath, fileNameFrom, '0' + fileNameTo);
}
}
}
copyFile('/tmp/test', 'a.txt');
Note: do not forget to change destination variable

How to search for files by extension and containing string within folder and subfolders?

Is it possible to look for files of given extension by the containing string provided?
What should my approach be? For example the input is txt and hello, and the output will be list of all files containing the string hello with extension txt.
You would write this in your terminal: node main.js hello
For a given directory it will search inside all subdirectories and all files for a text file with hello
Here is the code:
const { readdirSync, readFileSync, lstatSync } = require('fs');
const path = require('path');
const getDir = source => {
const results = readdirSync(source);
results.forEach(function (result) {
if (lstatSync(path.join(source, result))
.isFile()) {
if (readFileSync(path.join(source, result))
.includes(argument) && path.extname(result)
.toLowerCase() === extension) {
console.log("Your string is is in file: ", result)
}
}
else if (lstatSync(path.join(source, result))
.isDirectory()) {
getDir(path.join(source, result));
}
});
}
const dir = process.cwd();
const extension = '.txt'; //You can change the extension type here
let argument = process.argv[2];
getDir(dir);
You can use FileSystem to get the contents of a folder with the readdir method, this returns you an array of strings.
Let's say your working directory is a src file, in which there is a files folder that you want to read. Your script would look something like this:
const fs = require('fs');
var files = fs.readdir('./files', (err, filenames) => {
if (err) throw err;
return filenames;
})
You can then separate your string using string methods. Let's say you want to have two variables, fileNameand fileExt
You would use the String.split(separator) method, and use the . character as separator.
var files = // fs snippet above
for (file in files) {
let fileComponents = file.split('.');
let fileName = fileComponents[0];
let fileExt = fileComponents[1];
// You can run your code on the name, and extension of your file here.
}
This will not work for files containing multiple dots in their name. You will need extra work on the array to make sure fileName contains every string concatenated, separated by a . up until the final index of your fileComponents string

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.

Node.js check if path is file or directory

I can't seem to get any search results that explain how to do this.
All I want to do is be able to know if a given path is a file or a directory (folder).
The following should tell you. From the docs:
fs.lstatSync(path_string).isDirectory()
Objects returned from fs.stat() and fs.lstat() are of this type.
stats.isFile()
stats.isDirectory()
stats.isBlockDevice()
stats.isCharacterDevice()
stats.isSymbolicLink() // (only valid with fs.lstat())
stats.isFIFO()
stats.isSocket()
NOTE:
The above solution will throw an Error if; for ex, the file or directory doesn't exist.
If you want a true or false approach, try fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory(); as mentioned by Joseph in the comments below.
Update: Node.Js >= 10
We can use the new fs.promises API
const fs = require('fs').promises;
(async() => {
const stat = await fs.lstat('test.txt');
console.log(stat.isFile());
})().catch(console.error)
Any Node.Js version
Here's how you would detect if a path is a file or a directory asynchronously, which is the recommended approach in node.
using fs.lstat
const fs = require("fs");
let path = "/path/to/something";
fs.lstat(path, (err, stats) => {
if(err)
return console.log(err); //Handle error
console.log(`Is file: ${stats.isFile()}`);
console.log(`Is directory: ${stats.isDirectory()}`);
console.log(`Is symbolic link: ${stats.isSymbolicLink()}`);
console.log(`Is FIFO: ${stats.isFIFO()}`);
console.log(`Is socket: ${stats.isSocket()}`);
console.log(`Is character device: ${stats.isCharacterDevice()}`);
console.log(`Is block device: ${stats.isBlockDevice()}`);
});
Note when using the synchronous API:
When using the synchronous form any exceptions are immediately thrown.
You can use try/catch to handle exceptions or allow them to bubble up.
try{
fs.lstatSync("/some/path").isDirectory()
}catch(e){
// Handle error
if(e.code == 'ENOENT'){
//no such file or directory
//do something
}else {
//do something else
}
}
Seriously, question exists five years and no nice facade?
function isDir(path) {
try {
var stat = fs.lstatSync(path);
return stat.isDirectory();
} catch (e) {
// lstatSync throws an error if path doesn't exist
return false;
}
}
Depending on your needs, you can probably rely on node's path module.
You may not be able to hit the filesystem (e.g. the file hasn't been created yet) and tbh you probably want to avoid hitting the filesystem unless you really need the extra validation. If you can make the assumption that what you are checking for follows .<extname> format, just look at the name.
Obviously if you are looking for a file without an extname you will need to hit the filesystem to be sure. But keep it simple until you need more complicated.
const path = require('path');
function isFile(pathItem) {
return !!path.extname(pathItem);
}
If you need this when iterating over a directory (Because that's how I've found this question):
Since Node 10.10+, fs.readdir has a withFileTypes option which makes it return directory entry fs.Dirent instead of strings. Directory entries has a name property, and useful methods such as isDirectory or isFile, so you don't need to call fs.lstat explicitly.
import { promises as fs } from 'fs';
// ./my-dir has two subdirectories: dir-a, and dir-b
const dirEntries = await fs.readdir('./my-dir', { withFileTypes: true });
// let's filter all directories in ./my-dir
const onlyDirs = dirEntries.filter(de => de.isDirectory()).map(de => de.name);
// onlyDirs is now [ 'dir-a', 'dir-b' ]
Here's a function that I use. Nobody is making use of promisify and await/async feature in this post so I thought I would share.
const promisify = require('util').promisify;
const lstat = promisify(require('fs').lstat);
async function isDirectory (path) {
try {
return (await lstat(path)).isDirectory();
}
catch (e) {
return false;
}
}
Note : I don't use require('fs').promises; because it has been experimental for one year now, better not rely on it.
The answers above check if a filesystem contains a path that is a file or directory. But it doesn't identify if a given path alone is a file or directory.
The answer is to identify directory-based paths using "/." like --> "/c/dos/run/." <-- trailing period.
Like a path of a directory or file that has not been written yet. Or a path from a different computer. Or a path where both a file and directory of the same name exists.
// /tmp/
// |- dozen.path
// |- dozen.path/.
// |- eggs.txt
//
// "/tmp/dozen.path" !== "/tmp/dozen.path/"
//
// Very few fs allow this. But still. Don't trust the filesystem alone!
// Converts the non-standard "path-ends-in-slash" to the standard "path-is-identified-by current "." or previous ".." directory symbol.
function tryGetPath(pathItem) {
const isPosix = pathItem.includes("/");
if ((isPosix && pathItem.endsWith("/")) ||
(!isPosix && pathItem.endsWith("\\"))) {
pathItem = pathItem + ".";
}
return pathItem;
}
// If a path ends with a current directory identifier, it is a path! /c/dos/run/. and c:\dos\run\.
function isDirectory(pathItem) {
const isPosix = pathItem.includes("/");
if (pathItem === "." || pathItem ==- "..") {
pathItem = (isPosix ? "./" : ".\\") + pathItem;
}
return (isPosix ? pathItem.endsWith("/.") || pathItem.endsWith("/..") : pathItem.endsWith("\\.") || pathItem.endsWith("\\.."));
}
// If a path is not a directory, and it isn't empty, it must be a file
function isFile(pathItem) {
if (pathItem === "") {
return false;
}
return !isDirectory(pathItem);
}
Node version: v11.10.0 - Feb 2019
Last thought: Why even hit the filesystem?
I could check if a directory or file exists using this:
// This returns if the file is not a directory.
if(fs.lstatSync(dir).isDirectory() == false) return;
// This returns if the folder is not a file.
if(fs.lstatSync(dir).isFile() == false) return;
Function that returns type
I like coffee
type: (uri)-> (fina) ->
fs.lstat uri, (erro,stats) ->
console.log {erro} if erro
fina(
stats.isDirectory() and "directory" or
stats.isFile() and "document" or
stats.isSymbolicLink() and "link" or
stats.isSocket() and "socket" or
stats.isBlockDevice() and "block" or
stats.isCharacterDevice() and "character" or
stats.isFIFO() and "fifo"
)
usage:
dozo.type("<path>") (type) ->
console.log "type is #{type}"

Resources