Отслеживание хода архивирования с помощью архиватора в Node.js
Я использую архиватор, чтобы заархивировать каталог и отправить его пользователю моего веб-приложения. Все работает как положено, и есть код функции:
export async function packDirectory(directoryPath, archiveFilePath, options) {
options = options || {};
const {
archiverFormat = 'zip',
archiverOptions = {},
inputStreamOptions = { highWaterMark: 1024 * 1024 },
outputStreamOptions = { highWaterMark: 1024 * 1024 }
} = options;
const archive = archiver(archiverFormat, archiverOptions);
const outputStream = fs.createWriteStream(archiveFilePath, outputStreamOptions);
archive.pipe(outputStream);
const fileItems = await listDirectoryFiles(directoryPath);
for (let i = 0; i < fileItems.length; i++) {
const fileItem = fileItems[i];
const relativeFilePath = fileItem.path;
const filePath = path.resolve(directoryPath, relativeFilePath);
const inputStream = fs.createReadStream(filePath, inputStreamOptions);
archive.append(inputStream, {
name: relativeFilePath
});
}
return new Promise((resolve, reject) => {
archive.on('error', e => reject(e));
archive.on('warning', e => {
if (e.code !== 'ENOENT') {
reject(e);
}
});
outputStream.on('close', resolve);
archive.finalize();
});
}
Теперь я хочу, чтобы иметь возможность отслеживать ход молнии. archiver
предоставляет события входа и прогресса, но я не смог их использовать. Они запускаются только тогда, когда файл обрабатывается (добавляется в результирующий zip-файл), и это не позволяет плавно отслеживать процесс архивирования. Размеры архивируемых файлов значительно различаются, например, это содержимое каталога для упаковки (размер в байтах и имена файлов):
99 RP3_04203_03_GEOTON_20161213_033229_033233.L2.dbf
249 RP3_04203_03_GEOTON_20161213_033229_033233.L2.DC.xml
23562 RP3_04203_03_GEOTON_20161213_033229_033233.L2.MD.xml
257 RP3_04203_03_GEOTON_20161213_033229_033233.L2.prj
18721 RP3_04203_03_GEOTON_20161213_033229_033233.L2.QL.jpg
2668 RP3_04203_03_GEOTON_20161213_033229_033233.L2.shp
108 RP3_04203_03_GEOTON_20161213_033229_033233.L2.shx
81 RP3_04203_03_GEOTON_20161213_033229_033233.L2.tfw
135886758 RP3_04203_03_GEOTON_20161213_033229_033233.L2.tif
Таким образом, подсчет прогресса чтения входных файлов был бы приемлем для меня, и я попробовал следующее:
export async function packDirectory(directoryPath, archiveFilePath, options) {
// .......
const { onProgress } = options;
const fileItems = await listDirectoryFiles(directoryPath);
const overallBytes = fileItems.reduce((bytes, fileItem) => bytes + fileItem.stats.size, 0);
let bytes = 0;
for (let i = 0; i < fileItems.length; i++) {
// .......
const inputStream = fs.createReadStream(filePath, inputStreamOptions);
inputStream.on('data', chunk => {
bytes += chunk.length;
if (typeof onProgress === 'function') {
onProgress((bytes / overallBytes) * 100);
}
});
archive.append(inputStream, {
name: relativeFilePath
});
}
return new Promise(/*.......*/);
}
Расчет процентного значения передан onProgress
обратный вызов работает нормально, но как только я регистрирую слушателя data
событие на inputStream
, файлы в результирующем zip архиве становятся испорченными:
0 RP3_04203_03_GEOTON_20161213_033229_033233.L2.dbf
249 RP3_04203_03_GEOTON_20161213_033229_033233.L2.DC.xml
0 RP3_04203_03_GEOTON_20161213_033229_033233.L2.MD.xml
0 RP3_04203_03_GEOTON_20161213_033229_033233.L2.prj
0 RP3_04203_03_GEOTON_20161213_033229_033233.L2.QL.jpg
0 RP3_04203_03_GEOTON_20161213_033229_033233.L2.shp
0 RP3_04203_03_GEOTON_20161213_033229_033233.L2.shx
0 RP3_04203_03_GEOTON_20161213_033229_033233.L2.tfw
125400998 RP3_04203_03_GEOTON_20161213_033229_033233.L2.tif
Только первый обработанный файл (.DC.xml
) не сломан, все остальные имеют нулевой размер и даже последний большой .tif
размер файла меньше исходного. Что не так с моим кодом? Есть ли другое правильное решение для подсчета прочитанных байтов заархивированных файлов? Я никогда не ожидал, что простая регистрация слушателя (даже пустого) может изменить поведение.
Глядя на исходный код, я увидел, что Archiver
наследуется от Transform, но я не совсем знаком с ним. Кажется, что данные, прочитанные в моем слушателе, зарегистрированы для data
Событие по какой-то причине не успевает преобразоваться в выходной zip.
Я также пытался обрабатывать файлы последовательно:
const inputStream = fs.createReadStream(filePath, inputStreamOptions);
await (async() => {
return new Promise((resolve, reject) => {
inputStream.on('data', chunk => {
bytes += chunk.length;
if (typeof onProgress === 'function') {
onProgress((bytes / overallBytes) * 100)
}
});
inputStream.on('close', resolve);
inputStream.on('error', reject);
archive.append(inputStream, {
name: relativeFilePath
});
});
})();
Это дает лучшие результаты, но иногда некоторые файлы (.shx
) еще пусто
99 RP3_04203_03_GEOTON_20161213_033229_033233.L2.dbf
249 RP3_04203_03_GEOTON_20161213_033229_033233.L2.DC.xml
23562 RP3_04203_03_GEOTON_20161213_033229_033233.L2.MD.xml
257 RP3_04203_03_GEOTON_20161213_033229_033233.L2.prj
18721 RP3_04203_03_GEOTON_20161213_033229_033233.L2.QL.jpg
2668 RP3_04203_03_GEOTON_20161213_033229_033233.L2.shp
0 RP3_04203_03_GEOTON_20161213_033229_033233.L2.shx
81 RP3_04203_03_GEOTON_20161213_033229_033233.L2.tfw
135886758 RP3_04203_03_GEOTON_20161213_033229_033233.L2.tif