27.05.2021 в 11:33
Аким Солянкин
Рекурсивное извлечение Zip-файлов с помощью NodeJS
Недавно я столкнулся с требованием, при котором мне приходилось извлекать все zip-файлы, находящиеся внутри основного zip- файла, который имел случайную структуру папок, и любая из папок могла иметь zip-файл, присутствующий внутри нее на любом уровне.
Решение проблемы
- Найти библиотеку для распаковки
- Распакуйте основной zip-файл, т.е. demo.zip.
- Найдите способ рекурсивного обхода всей структуры папок
- Затем извлекайте файл
.zip
всякий раз, когда он будет найден.
Решение
Библиотека extract-zip, используется для извлечения файлов zip.
Метод извлечения zip-файла
- Он принимает два входных аргумента: источник и цель. source должен быть абсолютным путем к zip-файлу, target - куда будет извлечена папка.
async function extractZip(source, target) {
try {
await extract(source, { dir: target });
console.log("Extraction complete");
} catch (err) {
console.log("Oops: extractZip failed", err);
}
}
Метод рекурсивного обхода папок
const unzipFiles = async function (dirPath) {
const files = fs.readdirSync(dirPath);
await Promise.all(
files.map(async (file) => {
if (fs.statSync(dirPath + "/" + file).isDirectory()) {
await unzipFiles(dirPath + "/" + file);
} else {
const fullFilePath = path.join(dirPath, "/", file);
const folderName = file.replace(".zip", "");
if (file.endsWith(".zip")) {
zippedFiles.push(folderName);
await extractZip(fullFilePath, path.join(dirPath, "/", folderName));
await unzipFiles(path.join(dirPath, "/", folderName));
}
}
})
);
};
Много действий в приведенном выше фрагменте. Расшифруем
dirPath
: путь извлечения файла- Метод
fs.readdirSync()
используется для синхронного чтения содержимого данного каталога. Метод возвращает массив со всеми именами файлов или объектов в каталоге. - Теперь основная задача заключалается в том, чтобы асинхронно перебрать все папки / файлы. Мы не можем использовать
forEach
, так как он не поддерживает ключевое словоasync/await
. Традиционный синтаксис цикла for работает с ключевым словомawait
. Но я хотел использовать более распространенный метод массиваmap()
. - Если вы используете
await
сmap ()
, он возвращает массив обещаний. Следовательно, для разрешения всех обещаний здесь используетсяPromise.all(arrayOfPromises)
.
if (fs.statSync(dirPath + "/" + file).isDirectory()) {
await unzipFiles(dirPath + "/" + file);
}
Чтобы проверить, является ли текущий объект файлом или папкой, используется метод isDirectory ()
. Если это папка, то снова вызовите тот же метод, т. е. i.e unzipFiles()
else {
const fullFilePath = path.join(dirPath, "/", file);
const folderName = file.replace(".zip", "");
if (file.endsWith(".zip")) {
zippedFiles.push(folderName);
await extractZip(fullFilePath, path.join(dirPath, "/", folderName));
await unzipFiles(path.join(dirPath, "/", folderName));
}
- Если файл найден, мы вызовем метод
extractZip()
сsource
иtarget
с их абсолютными путями. - Если мы не укажем
target
или дадим ему текущий путь, он сам распакует все файлы в текущем каталоге. Но я хотел распаковать zip в соответствующие имена папок. - Чтобы добиться этого, я склеил имя папки из файла .zip, передав его в качестве
target
для метода
.Теперь в последней строке есть еще одна загвоздка, т.е.extractZip()
await unzipFiles(path.join(dirPath, "/", folderName));
- Поскольку существует вероятность того, что извлеченные файлы также могут содержать внутри себя zip-файлы, поэтому, как только мы извлечем какой-либо файл, нам снова придется вызывать
unzipFiles()
для просмотра извлеченных файлов.
Выход будет
Большое спасибо за чтение 🙏