Rename files inside zip archive in nodejs - node.js

I am writing a nodejs script which should do the following:
Download a zip file
Remove the top level directory of the zip file (moving all files one folder up)
Upload the new zip file
Because the zip file is rather large, I would like to rename (or move) the files without unzipping and rezipping the file.
Is that possible?

Yes, it's possible
Using a library like adm-zip
var AdmZip = require('adm-zip');
//create a zip object to hold the new zip files
var newZip = new AdmZip();
// reading archives
var zip = new AdmZip('somePath/download.zip');
var zipEntries = zip.getEntries(); // an array of ZipEntry records
zipEntries.forEach(function(zipEntry) {
var fileName = zipEntry.entryName;
var fileContent = zip.readAsText(fileName)
//Here remove the top level directory
var newFileName = fileName.substring(fileName.indexOf("/") + 1);
newZip.addFile(newFileName, fileContent, '', 0644 << 16);
});
newZip.writeZip('somePath/upload.zip'); //write the new zip
Algorithm
Create a newZip object to temporarily hold files in memory
Read all entries in the downloaded zip. For each entry
Read the fileName. This includes the path
Read the file content using the fileName
Remove the top level directory name to get the newFileName
Add the fileContent in step 2 to the newZip giving it the newFileName from step 3
Finally, write out the newZip to disk giving it a new zipName
Hope that helps

You can use the great jszip library with async-promise style.
import jszip from 'jszip';
import fs from 'fs';
/**
* Move/rename entire directory tree within a zip.
* #param {*} zipFilePath The original zip file
* #param {*} modifiedZipFilePath The path where palace the modified zip
* #param {*} originalDir The original directory to change
* #param {*} destinationDir The new directory to move to.
*/
async function moveDirectory(zipFilePath, modifiedZipFilePath, originalDir, destinationDir) {
// Read zip file bits buffer
const zipFileBuffer = await fs.promises.readFile(zipFilePath);
// Load jszip instance
const zipFile = await jszip.loadAsync(zipFileBuffer);
// Get the original directory entry
const originalDirContent = zipFile.folder(originalDir);
// Walk on all directory tree
originalDirContent.forEach((path, entry) => {
// If it's a directory entry ignore it.
if (entry.dir) {
return;
}
// Extract the file path within the directory tree
const internalDir = path.split(originalDir)[0];
// Build the new file directory in the new tree
const newFileDir = `${destinationDir}/${internalDir}`;
// Put the file in the new tree, with the same properties
zipFile.file(newFileDir, entry.nodeStream(), {
createFolders: true,
unixPermissions: entry.unixPermissions,
comment: entry.comment,
date: entry.date,
});
});
// After all files copied to the new tree, remove the original directory tree.
zipFile.remove(originalDir);
// Generate the new zip buffer
const modifiedZipBuffer = await zipFile.generateAsync({ type: 'nodebuffer' });
// Save the buffer as a new zip file
await fs.promises.writeFile(modifiedZipFilePath, modifiedZipBuffer);
}
moveDirectory('archive.zip', 'modified.zip', 'some-dir/from-dir', 'some-other-dir/to-dir');
This is simply walking on all original directory tree entries and place them in the new directory tree.

Related

How to edit this code to wait for a file fully uploaded with node.js?

Hi I am trying to download a file and when it downloads it first has the extension .tmp or .crdownload, then after it is fully downloaded the extension changes to whatever the correct extension is, for example .png. I want to wait until the file no longer has the extension .tmp or .crdownload and then save the new file path to a variable. How can I do this? I have looked here on Stack Overflow but it is not answering all my questions. This is a unique and specific question. That is what I have so far. I don't mind if you don't use this code as long as the resulting code waits for the extension to change and saves the path to a variable as a string.
Code:
var downloadanduploadpath = "C:/Users/user1/Downloads";
//ptc mean path to check. have differnt name so the variable name of the placeholder in the function call is not the same name as the variable name in the function
var ptc = downloadanduploadpath;
var pathtocheck = ptc;
var timetowaitforimagefiledownloaded = 5000;
var fs = require('fs');
//let dirToCheck = pathtocheck;
var filecheckerfiles = fs.readdirSync(pathtocheck);
var filecheckerlatestPath = `${pathtocheck}/${filecheckerfiles[0]}`;
var filecheckerlatestTimeStamp = fs.statSync(filecheckerlatestPath).mtime.getTime();
filecheckerfiles.forEach(filecheckerfile => {
var filecheckerpath = `${pathtocheck}/${filecheckerfile}`;
var filecheckertimeStamp = fs.statSync(filecheckerpath).mtime.getTime();
if (filecheckertimeStamp > filecheckerlatestTimeStamp) {
filecheckerlatestTimeStamp = filecheckertimeStamp;
lastdownloadedimagetimestamp = filecheckerlatestTimeStamp;
lastdownloadedimage = filecheckerpath;
}
});
//get the extention of the last downloaded image
var lastdownloadedimageextention = lastdownloadedimage.split(".").pop();
//"png" || "jpg" || "jpeg" || "gif" ||
//if the file is a .tmp the image is not fully downloaded. run code inside to wait for it to not be a tmp file showing that the image if fully downloaded
if((lastdownloadedimageextention == "tmp") || (lastdownloadedimageextention == "crdownload") ){
//Show the file path to the console
console.log(lastdownloadedimage);
//show file not fill downloaded message in console
console.log("Image not yet fully downloaded.");
//show preparing to wait message in console
console.log("Preparing to wait for full download.");
//get all the files in the folder directory/path
var filecheckerfindpathbytimestamp = fs.readdirSync(pathtocheck);
console.log("hi1");
//define variable to store the path of the last donwloaded image foudn by timestamp
var lastdownloadedimagefoundbytimestamppath;
console.log("hi2");
//use the timestamp as an index to get the last downloaded file and then check the file at this timestamp(the last downloaded image) and check its file path.
//using the timestamp as an image lets us check the file without using the name because the name changes from .tmp to .png for example, so we are unable to get the
//the file by its name because of this name , so we use the timestamp to specifc the file we want to check the extention of instead
//find path of the last downloaded file by using the time to find it
filecheckerfindpathbytimestamp.forEach(filecheckerfindpathbytimestampfile => {
var filecheckerfindpathbytimestamppath = `${pathtocheck}/${filecheckerfindpathbytimestampfile}`;
console.log("hi3");
var filecheckerfindpathbytimestamptimeStamp = fs.statSync(filecheckerfindpathbytimestamppath).mtime.getTime();
console.log("hi4");
if (filecheckerfindpathbytimestamptimeStamp == lastdownloadedimagetimestamp) {
console.log("hi5");
lastdownloadedimagefoundbytimestamppath = filecheckerfindpathbytimestamppath;
console.log("hi6");
}
});
console.log("hi7");
//keep checking the file that ends in .tmp untill it changes from .tmp . once the file is no longer endng in .tmp indicating that the file is
//fully downloaded this path that is looking for the fiel path including the .tmp will no longer be valid because of the file path becomeing .png for example
// that it has fully downloaded, and the file path will not return true because it is no longer valid with the .tmp extention.
//once this path is no longer valid we will know that the file has fully downloaded because that path no longer ends in .tmp. We can then end the while loop.
//because file if fully downloaded.
//
while(fs.existsSync(lastdownloadedimagefoundbytimestamppath) == true){
//while((fs.existsSync(lastdownloadedimage)) == true){
//Show the file path to the console
console.log(lastdownloadedimage);
//Wait code
//Wait on the image converting page
await page2.type('.jsoninputtextarea', " ", {delay:timetowaitforimagefiledownloaded});
//show waiting for file fully download message in console
console.log("Waiting for image to fully download.");
}
//create varaible to find path of the last downloaded file by using the time to find it now that it is fully downloaded
var filecheckerfindfullydownloadedfiletimestamp = fs.readdirSync(pathtocheck);
//find path of the last downloaded file by using the time to find it now that it is fully downloaded
filecheckerfindfullydownloadedfiletimestamp.forEach(filecheckerfindfullydownloadedfilefile => {
var filecheckerfindfullydownloadedfilepath = `${pathtocheck}/${filecheckerfindfullydownloadedfilefile}`;
console.log("hi8");
var filecheckerfindfullydownloadedfiletimeStamp = fs.statSync(filecheckerfindfullydownloadedfilepath).mtime.getTime();
console.log("hi9");
if (filecheckerfindfullydownloadedfiletimeStamp == lastdownloadedimagetimestamp) {
console.log("hi10");
lastdownloadedimagefoundbytimestamppath = filecheckerfindfullydownloadedfilepath;
console.log("hi11");
}
});
console.log("hi12");
//Set lastdownloaded image to the path the now ends in the file extention (example .png) now instead of .tmp
//
lastdownloadedimage = lastdownloadedimagefoundbytimestamppath;
}
//Remove the path the of the image so we only have image name and file type
//Before: C:/Users/edtec/Downloads/image.png After: image.png
lastdownloadedimage = lastdownloadedimage.replace(downloadanduploadpath, "");
//Return lastdownloadedimage
//return lastdownloadedimage;
//}
//lastdownloadedimage = getlastdownloadedimage(downloadanduploadpath);
//console.log(lastdownloadedimage);
console.log(lastdownloadedimage);

Problem with traversing file system using Node.js

I keep getting the error 'Error: ENOENT: no such file or directory, open 't1.txt''
when I run the function starting in the top-level directory pictured below.
I think it has to with 'fs.readFileSync()' attempting to read a file's contents from a directory different than the one fs is declared in, though I am not quite sure.
Picture of directory structure
/* the built-in Node.js 'fs' module is included in our program so we can work with the computer's file system */
const fs = require('fs');
// used to store the file contents of the files encountered
const fileContents = {};
/* stores the duplicate files as subarrays within the array, where the first element of the subarray is the duplicate file, and the second element is the original */
let duplicateFiles = [];
const traverseFileSystem = function (currentPath) {
// reads the contents of the current directory and returns them in an array
let files = fs.readdirSync(currentPath);
// for-in loop is used to iterate over contents of directory
for (let i in files) {
let currentFile = currentPath + '/' + files[i];
// retrieves the stats of the current file and assigns them to a variable
let stats = fs.statSync(currentFile);
// it's determined if the 'currentFile' is actually a file
if (stats.isFile()) {
/* if the file's contents are in the 'fileContents' object, then a new file has been encountered */
if(fileContents[fs.readFileSync(files[i])] === undefined) {
// the file's contents are set as the key, and the file path as the value
fileContents[fs.readFileSync(files[i])] = currentFile;
}
// otherwise, the file's contents already exist in the 'fileContents' object, which means a duplicate file has been found
else {
duplicateFiles.push([fileContents[fs.readFileSync(files[i])], currentFile]);
}
}
/* if the 'file' is actually a directory, traverseFileSystem() is called recursively on that directory */
else if (stats.isDirectory()) {
traverseFileSystem(currentFile);
}
}
return duplicateFiles;
};
You have fs.readFileSync(files[i]) which should be fs.readFileSync(currentFile) based on your code. Haven't confirmed your logic, but this should solve the error you are currently getting.

Compress an uncompressed xlsx file using node.js (Electron)

I have an unzipped xlsx file, in it I edit some files to be able to generate a new xlsx file containing new data.
In linux to recompress the file in xlsx I just need to go into the terminal and type
find . -type f | xargs zip ../newfile.xlsx
into the folder where the xlsx files are.
The question now is how can I do this using node.js?
The solution is to compress a direct list of files contained in xlsx, for some reason if we try to compress the folder the file has corrupted.
The code looks like this if you use JSZIP
var fs = require('fs');
var JSZip = require("jszip");
var zip = new JSZip();
var file = [];
file.push("_rels/.rels");
file.push("docProps/core.xml");
file.push("docProps/app.xml");
file.push("docProps/custom.xml");
file.push("[Content_Types].xml");
file.push("xl/_rels/workbook.xml.rels");
file.push("xl/styles.xml");
file.push("xl/pivotTables/_rels/pivotTable3.xml.rels");
file.push("xl/pivotTables/_rels/pivotTable1.xml.rels");
file.push("xl/pivotTables/_rels/pivotTable2.xml.rels");
file.push("xl/pivotTables/pivotTable3.xml");
file.push("xl/pivotTables/pivotTable1.xml");
file.push("xl/pivotTables/pivotTable2.xml");
file.push("xl/workbook.xml");
file.push("xl/worksheets/_rels/sheet2.xml.rels");
file.push("xl/worksheets/_rels/sheet1.xml.rels");
file.push("xl/worksheets/_rels/sheet3.xml.rels");
file.push("xl/worksheets/sheet4.xml");
file.push("xl/worksheets/sheet1.xml");
file.push("xl/worksheets/sheet3.xml");
file.push("xl/worksheets/sheet2.xml");
file.push("xl/sharedStrings.xml");
file.push("xl/pivotCache/_rels/pivotCacheDefinition1.xml.rels");
file.push("xl/pivotCache/pivotCacheDefinition1.xml");
file.push("xl/pivotCache/pivotCacheRecords1.xml");
for (var i = 0; i < file.length; i++) {
zip.file(file[i], fs.readFileSync("/home/user/xlsx_FILES/"+file[i]));
}
zip.generateAsync({type:"blob"}).then(function(content) {
// see FileSaver.js
saveAs(content, "yourfile.xlsx");
});
Take a look at archiver, a compression library for nodejs. The docs for the library look like they are comprehensive. The library also allows you to append archives and take advantage of streaming api's for appending and creating new archives.
Here is an example snippet from their docs which shows how to use the library.
// require modules
var fs = require('fs');
var archiver = require('archiver');
// create a file to stream archive data to.
var output = fs.createWriteStream(__dirname + '/example.zip');
var archive = archiver('zip', {
store: true // Sets the compression method to STORE.
});
// listen for all archive data to be written
output.on('close', function() {
console.log(archive.pointer() + ' total bytes');
console.log('archiver has been finalized and the output file descriptor has closed.');
});
// good practice to catch this error explicitly
archive.on('error', function(err) {
throw err;
});
// pipe archive data to the file
archive.pipe(output);

NodeJS - Copy and Rename all contents in existing directory recursively

I have a directory with folders and files within. I want to copy the entire directory with all its contents to a different location while renaming all the files to something more meaningful. I want to use nodejs to complete this series of operations. What is an easy way to do it, other than moving it one by one and renaming it one by one?
Thanks.
-- Thanks for the comment! So here is an example directory that I have in mind:
-MyFridge
- MyFood.txt
- MyApple.txt
- MyOrange.txt
- ...
- MyDrinks
- MySoda
- MyDietCoke.txt
- MyMilk.txt
- ...
- MyDesserts
- MyIce
...
I want to replace "My" with "Tom," for instance, and I also would like to rename "My" to Tom in all the text files. I am able to copy the directory to a different location using node-fs-extra, but I am having a hard time with renaming the file names.
Define your own tools
const fs = require('fs');
const path = require('path');
function renameFilesRecursive(dir, from, to) {
fs.readdirSync(dir).forEach(it => {
const itsPath = path.resolve(dir, it);
const itsStat = fs.statSync(itsPath);
if (itsPath.search(from) > -1) {
fs.renameSync(itsPath, itsPath.replace(from, to))
}
if (itsStat.isDirectory()) {
renameFilesRecursive(itsPath.replace(from, to), from, to)
}
})
}
Usage
const dir = path.resolve(__dirname, 'src/app');
renameFilesRecursive(dir, /^My/, 'Tom');
renameFilesRecursive(dir, /\.txt$/, '.class');
fs-jetpack has a pretty nice API to deal with problems like that...
const jetpack = require("fs-jetpack");
// Create two fs-jetpack contexts that point
// to source and destination directories.
const src = jetpack.cwd("path/to/source/folder");
const dst = jetpack.cwd("path/to/destination");
// List all files (recursively) in the source directory.
src.find().forEach(path => {
const content = src.read(path, "buffer");
// Transform the path however you need...
const transformedPath = path.replace("My", "Tom");
// Write the file content under new name in the destination directory.
dst.write(transformedPath, content);
});

Unzipping a zipped folder in nodejs

I zipped a folder in nodejs with code :
fstream = require('fstream'),
tar = require('tar'),
zlib = require('zlib');
fstream.Reader(toZipDetails) /* Read the source directory */
.pipe(tar.Pack()) /* Convert the directory to a .tar file */
.pipe(zlib.Gzip()) /* Compress the .tar file */
.pipe(fstream.Writer(zipOutDetails)); /* Give the output file name */
Then i unzipped it with :
fs.createReadStream(inFileName)
.pipe(zlib.Gunzip())
.pipe(tar.Extract({ path: "C:\\temp\\extract" }))
.on("end", function () {
alert("done");
});
The folder name is toZip with file a.txt.
I wanted a folder toZip with a.txt in extract folder, but
i got a a.txt file in extract folder.
How can i get the toZip folder ?
You can try :
var unzip = require('unzip')
var fs = require('fs');
fs.createReadStream('<<.zip folder path>>').pipe(unzip.Extract({ path: <<save unzipped data path>> }));

Resources