Javascript-форум (https://javascript.ru/forum/)
-   Node.JS (https://javascript.ru/forum/node-js-io-js/)
-   -   Как избежать ошибки по ограничению открытых файлов? (https://javascript.ru/forum/node-js-io-js/74928-kak-izbezhat-oshibki-po-ogranicheniyu-otkrytykh-fajjlov.html)

Tipylja 17.08.2018 15:52

Как избежать ошибки по ограничению открытых файлов?
 
Здравствуйте. Подскажите пожалуйста. Недавно я писал на этом форуме с вопросом, о том, как создать структуру файлов и попок из исходного каталога в другом каталоге
https://javascript.ru/forum/node-js-...j-sisteme.html
Для копирование структуры я нашел и доработал под себя вот такую функцию:
function walkSync (dir, filelist = []) {
    fs.readdirSync(dir).forEach(file => {
        const dirFile = path.join(dir, file);        
        replaceDirPath = dir.replace("sources\\products\\", replaceDir + value + "/");
         if(!fs.existsSync(replaceDirPath)){
                fs.mkdirSync(replaceDirPath);
         }

    try {
        filelist = walkSync(dirFile, filelist);

    }
    catch (err) {
        if (err.code === 'ENOTDIR' || err.code === 'EBUSY') filelist = [...filelist, dirFile];
    else throw err;
    }
});


В цикле получаем пути папок в переменной dir, далее меняем в ней путь на нужный (что бы создать данную структуру в другом каталоге) и записываем в replaceDirPath, тут же и создаем каталоги по пути replaceDirPath.
Второй момент - мне необходимо кроме того, что создать структуру в другом каталоге, так еще и вложить всю эту структуру в несколько каталогов - у меня это разные размеры фотографий. Что бы было понятнее, я объясню задачу вообще, что именно я хочу:
Есть папка с исходными фотографиями, со своей структурой, из это папки нужно сделать несколько папок-копий, но фотографии в каждой из папок будут иметь разный размер, то есть выходит такая структура:
Цитата:

-ИСХОДНИКИ
--Категория 1
---Подкатегория 1
----Изображение 1
----Изображение 2
----Изображение 3
----Изображение 4
---Подкатегория 2
--Категория 2
---Подкатегория 1
----Изображение 1
----Изображение 2
----Изображение 3
Из этой структуры мне нужно получить
Цитата:

-ОБРАБОТАННЫЕ
--400х400
--Категория 1
---Подкатегория 1
----Изображение 1
----Изображение 2
----Изображение 3
----Изображение 4
---Подкатегория 2
--Категория 2
---Подкатегория 1
----Изображение 1
----Изображение 2
----Изображение 3
--200х200
--Категория 1
---Подкатегория 1
----Изображение 1
----Изображение 2
----Изображение 3
----Изображение 4
---Подкатегория 2
--Категория 2
---Подкатегория 1
----Изображение 1
----Изображение 2
----Изображение 3
--80х80
--Категория 1
---Подкатегория 1
----Изображение 1
----Изображение 2
----Изображение 3
----Изображение 4
---Подкатегория 2
--Категория 2
---Подкатегория 1
----Изображение 1
----Изображение 2
----Изображение 3
Для начала я делаю объект, который состоит из названия папок-размеров(те что выделены жирным в структуре) и настроек (на данном этапе это размер самих фотографий, который я буду передавать в модуль обработки фото):
// Размеры картинок
var settings = {
    '400x400':'400, 400' //КАРТОЧКА ТОВАРА: Основная фотография
    ,'80x80':'80, 80' //КАРТОЧКА ТОВАРА: сопутсвующий товар
    ,'200x200':'200, 200' //КАРТОЧКА ТОВАРА: хит продаж
    ,'120x120':'120, 120' //КАТЕГОРИИ: может пригодиться
    ,'265x265':'265, 265' //КАТЕГОРИИ: Фото позици
    ,'190x190':'190, 190' //КАТЕГОРИИ: хит продаж
    ,'175x175':'175, 175' //ОБЩИЕ: просмотренные

};

Далее я переделал первую функцию, walkSync() так, что бы она в структуре сразу же создала и папки 400x400, 80x80, 200x200 и т.д. Для этого в функцию я добавил цикл для объекта settings:

function walkSync (dir, filelist = []) {
    fs.readdirSync(dir).forEach(file => {
        const dirFile = path.join(dir, file);
        for(value in settings){
            if(!fs.existsSync("./products/" + value + "/")){
                fs.mkdirSync("./products/" + value + "/");
            }
            replaceDirPath = dir.replace("sources\\products\\", replaceDir + value + "/");
            if(!fs.existsSync(replaceDirPath)){
                fs.mkdirSync(replaceDirPath);
            }
        }


    try {
        filelist = walkSync(dirFile, filelist);

    }
    catch (err) {
        if (err.code === 'ENOTDIR' || err.code === 'EBUSY') filelist = [...filelist, dirFile];
    else throw err;
    }
});

Теперь эта функция полностью создала мне нужную структуру. Далее я пробую по этой структуре раскидать копии файлов из исходников
Для этого результат работы функции, массив, я принимаю в переменную list и прохожусь по нему циклом, копируя файлы по папкам из исходного каталога

var list = walkSync(dir);

list.forEach(function (v) {
    var dest = v,
        target = v.replace("sources\\products\\", replaceDir);
    fs.copyFile(dest, target, 0,(err)=>{
        if(err) console.log(err);
    });
});

Все работает как нужно, копии раскидались по нужным папкам.
Теперь пришло время основной задачи - обработка фотографий, для этого используется модуль jimp

Теперь, вместо копирования (fs.copyFile) я пользуюсь модулем jimp, ему нужно указать откуда берем файл и куда кладем - то есть по сути он сам занимается копированием, делаю функцию обработки фото:
function renderImg(dest, target){
    jimp.read(dest, (err, img) => {
        if (err) throw err;
        img
            //.resize() // resize
            //.quality(60) // set JPEG quality
            //.greyscale()
            .write(target); // save
});
}

и добавляю ее в цикл, вместо функции копирования, теперь цикл выглядит так:
//Цикл обработки и размещения файлов
list.forEach(function (v) {
    var dest = v;
    for(value in settings){
        var target = v.replace("sources\\products\\", replaceDir  + value + "\\");
        renderImg(dest, target); //Обработка файлов и копирование
    }


});

Но тут я и получаю ошибку "Error: EMFILE: too many open files"
Как я понимаю jimp долго обрабатывает каждый файл и не успевает выполнить то, что ему дает цикл, таким образом открытых файлов становиться больше, чем можно, в моем случае это 256. Фотографий в исходной папке около 1700 на данный момент, но они еще и умножаться на количество папок-размеров, то есть больше 10к.

Подскажите пожалуйста, как можно избежать такой проблемы? Опыта у меня мало, чувствую, что код написан коряво, но я это писал несколько дней, уже не знаю куда и смотреть. Возможно, как-то можно тормозить цикл и возбуждать его по событию или как-то вообще все по другому сделать? Буду рад любому пояснению

Tipylja 17.08.2018 15:54

Вот мой код целиком
var fs = require('fs')
    ,path = require('path')
    ,jimp = require('jimp')


// Настройки
var dir = "sources/products",
    replaceDirPath= "",
    replaceDir = "./products/";


// Название папок и размеры картинок
var settings = {
    '400x400':'400, 400' //КАРТОЧКА ТОВАРА: Основная фотография
    ,'80x80':'80, 80' //КАРТОЧКА ТОВАРА: сопутсвующий товар
    ,'200x200':'200, 200' //КАРТОЧКА ТОВАРА: хит продаж
    ,'120x120':'120, 120' //КАТЕГОРИИ: может пригодиться
    ,'265x265':'265, 265' //КАТЕГОРИИ: Фото позици
    ,'190x190':'190, 190' //КАТЕГОРИИ: хит продаж
    ,'175x175':'175, 175' //ОБЩИЕ: просмотренные
};

// Обработка картинок-файлов
function renderImg(dest, target){
    jimp.read(dest, (err, img) => {
        if (err) throw err;
        img
            //.resize() // resize
            //.quality(60) // set JPEG quality
            //.greyscale()
            .write(target); // save
});
}


function walkSync (dir, filelist = []) {
    fs.readdirSync(dir).forEach(file => {
        const dirFile = path.join(dir, file);
        for(value in settings){
            if(!fs.existsSync("./products/" + value + "/")){
                fs.mkdirSync("./products/" + value + "/");
            }
            replaceDirPath = dir.replace("sources\\products\\", replaceDir + value + "/");
            if(!fs.existsSync(replaceDirPath)){
                fs.mkdirSync(replaceDirPath);
            }
        }


    try {
        filelist = walkSync(dirFile, filelist);

    }
    catch (err) {
        if (err.code === 'ENOTDIR' || err.code === 'EBUSY') filelist = [...filelist, dirFile];
    else throw err;
    }
});

    return filelist;
}

var list = walkSync(dir);

//Цикл обработки и размещения файлов
list.forEach(function (v) {
    var dest = v;
    for(value in settings){
        var target = v.replace("sources\\products\\", replaceDir  + value + "\\");
        renderImg(dest, target);
        // fs.copyFile(dest, target, 0,(err)=>{
        //     if(err) console.log(err);
        // });
    }


});

Aetae 17.08.2018 17:40

Не получилось у вас убежать от асинхронности:)
Делать придётся примерно так:
function renderImg(dest, target){
    return jimp.read(dest).then(img => {
      return img.write(target)
    });
};

function oneByOne(array, func, errorCallback) {
  return array.reduce((p, arguments)=> p.then(
      result => func.apply(this, arguments),
      error => {
        if(errorCallback) errorCallback(error);
        return func.apply(this, arguments);
      },
  ), Promise.resolve()); 
};


var argumentsList = list.reduce((array, dest) => {
    for(value in settings){
      array.push([
        dest,
        dest.replace("sources\\products\\", replaceDir  + value + "\\")
      ])
    }
}, []);



oneByOne(argumentsList, renderImg);

Tipylja 17.08.2018 19:43

Цитата:

Сообщение от Aetae (Сообщение 492984)
Не получилось у вас убежать от асинхронности:)
Делать придётся примерно так

Благодарю, буду пробовать дальше разбираться.


Часовой пояс GMT +3, время: 08:31.