import { CompletedTypeEnum, StatusParseEnum } from '@common/ParseCommon';
import { intervalToDuration } from 'date-fns';
import { app, dialog, ipcMain, shell } from 'electron';
import fs from 'fs';
import puppeteer from 'puppeteer';
const path = require("path");
const { Worker } = require("worker_threads");
const activeProcesses = {}
let processIdCounter = 0
export function registerIPCHandlers(mainWindow, cpuCores) {
function sendProcessListUpdate() {
//отправка актуального списка в рендерер
mainWindow.webContents.send('process-list-update', activeProcessesCut());
}
ipcMain.handle('start-parser', (_event, { config }) => {
if (Object.keys(activeProcesses).length >= cpuCores) {
return
}
processIdCounter++;
const processId = processIdCounter;
let urlq = ''
let isPackaged = true
if (app.isPackaged) {
//режим продакшн
isPackaged = true
urlq = path.join(app.getAppPath(), 'out', 'main')
} else {
//режим разработки
isPackaged = false
urlq = path.join(app.getAppPath(), 'src', 'main')
}
const workerPath = app.isPackaged
? path.join(process.resourcesPath, 'app.asar', 'out', 'main', 'workers', 'parserWorker.js')
: path.join(app.getAppPath(),'src', 'main', 'workers', 'parserWorker.js');
let worker = new Worker(workerPath, {
workerData: { config, urlq , processId, isPackaged },
type: 'module'
})
activeProcesses[processId] = {
id: processId,
name: config.nameProcess,
worker,
config,
status: StatusParseEnum.Active,
typeCompleted: null,
startTime: Date.now(),
finishTime: 0,
folder: '',
elapsedTime: 0,
progress: 0,
selected: false
};
worker.on('message', (message) => {
if (message.type == StatusParseEnum.Active) {
activeProcesses[processId].folder = message.folder;
activeProcesses[processId].progress = +message.percent;
const elapsedTime = new Date().getTime() - activeProcesses[processId].startTime;
const estimatedTotalTime = elapsedTime / (message.percent / 100);
const remainingTime = estimatedTotalTime - elapsedTime;
const duration = intervalToDuration({ start: 0, end: remainingTime });
// Ручное форматирование продолжительности
const hours = duration.hours || 0; // Обрабатываем undefined или null
const minutes = duration.minutes || 0;
const seconds = duration.seconds || 0;
const formattedTime = `${hours < 10 ? '0' : ''}${hours}:${minutes < 10 ? '0' : ''}${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
activeProcesses[processId].finishTime = formattedTime;
} else if (message.type == StatusParseEnum.Completed) {
activeProcesses[processId].status = StatusParseEnum.Completed;
activeProcesses[processId].finishTime = Date.now()
activeProcesses[processId].typeCompleted = CompletedTypeEnum.Programm
activeProcesses[processId].progress = +message.percent;
activeProcesses[processId].folder = message.folder;
} else if (message.type == StatusParseEnum.Error) {
activeProcesses[processId].status = StatusParseEnum.Error;
activeProcesses[processId].finishTime = Date.now()
activeProcesses[processId].typeCompleted = CompletedTypeEnum.Programm
activeProcesses[processId].folder = message.folder;
}
sendProcessListUpdate(); // Обновляем список процессов в Renderer
})
});
ipcMain.handle('restart-process', async (_event, processId ) => {
if (Object.keys(activeProcesses).length >= cpuCores) {
return
}
const config = activeProcesses[processId].config
let urlq = ''
let isPackaged = true
if (app.isPackaged) {
//режим продакшн
isPackaged = true
urlq = path.join(app.getAppPath(), 'out', 'main')
} else {
//режим разработки
isPackaged = false
urlq = path.join(app.getAppPath(), 'src', 'main')
}
const workerPath = app.isPackaged
? path.join(process.resourcesPath, 'app.asar', 'out', 'main', 'workers', 'parserWorker.js')
: path.join(app.getAppPath(),'src', 'main', 'workers', 'parserWorker.js');
let worker = new Worker(workerPath, {
workerData: { config, urlq , processId, isPackaged },
type: 'module'
})
activeProcesses[processId].worker = worker
activeProcesses[processId].status = StatusParseEnum.Active
activeProcesses[processId].startTime = Date.now()
activeProcesses[processId].finishTime = 0
activeProcesses[processId].progress = 0
worker.on('message', (message) => {
if (message.type == StatusParseEnum.Active) {
activeProcesses[processId].progress = +message.percent;
const elapsedTime = new Date().getTime() - activeProcesses[processId].startTime;
const estimatedTotalTime = elapsedTime / (message.percent / 100);
const remainingTime = estimatedTotalTime - elapsedTime;
const duration = intervalToDuration({ start: 0, end: remainingTime });
// Ручное форматирование продолжительности
const hours = duration.hours || 0; // Обрабатываем undefined или null
const minutes = duration.minutes || 0;
const seconds = duration.seconds || 0;
const formattedTime = `${hours < 10 ? '0' : ''}${hours}:${minutes < 10 ? '0' : ''}${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
activeProcesses[processId].finishTime = formattedTime;
} else if (message.type == StatusParseEnum.Completed) {
activeProcesses[processId].status = StatusParseEnum.Completed;
activeProcesses[processId].finishTime = Date.now()
activeProcesses[processId].typeCompleted = CompletedTypeEnum.Programm
activeProcesses[processId].progress = +message.percent;
}
sendProcessListUpdate(); // Обновляем список процессов в Renderer
})
return activeProcessesCut()
});
ipcMain.handle('get-processes', async () => { // Используем handle для ответа на запрос
return activeProcessesCut()
});
function activeProcessesCut() {
return Object.values(activeProcesses).map((processInfo: any) => ({
id: processInfo.id,
name: processInfo.name,
status: processInfo.status,
startTime: processInfo.startTime,
finishTime: processInfo.finishTime,
typeCompleted: processInfo.typeCompleted,
elapsedTime: processInfo.elapsedTime,
progress: processInfo.progress,
selected: processInfo.selected,
folder: processInfo.folder
}))
}
ipcMain.handle('delete-process', async (_event, processId ) => {
const worker = activeProcesses[processId].worker
worker.terminate(); // Завершаем worker после завершения задачи
delete activeProcesses[processId]; // Удаляем процесс из активных
return activeProcessesCut(); // Обновляем список процессов в Renderer
})
ipcMain.handle('stop-process', async (_event, processId ) => {
const worker = activeProcesses[processId].worker
worker.terminate(); // Завершаем worker после завершения задачи
activeProcesses[processId].status = StatusParseEnum.Completed
activeProcesses[processId].finishTime = Date.now()
activeProcesses[processId].typeCompleted = CompletedTypeEnum.Manually
return activeProcessesCut(); // Обновляем список процессов в Renderer
})
ipcMain.handle('select-process', async (_event, processId ) => {
activeProcesses[processId].selected = !activeProcesses[processId].selected
return activeProcessesCut(); // Обновляем список процессов в Renderer
})
ipcMain.handle('show-open-dialog', async () => {
const { filePaths } = await dialog.showOpenDialog({
title: 'Выберите папку',
properties: ['openDirectory'], // Разрешаем выбор только папок
});
return filePaths[0]; // Возвращаем путь к выбранной папке
})
ipcMain.handle('open-folder', async (_event, folderPath) => {
shell.openPath(folderPath)
})
// Обработчик для записи файла
ipcMain.handle('write-file', async (_event, filePath, content) => {
try {
fs.writeFileSync(filePath, content);
return true;
} catch (error) {
console.error('Ошибка при записи файла:', error);
return false;
}
})
ipcMain.handle('get-screenshot', async (_event, url) => {
if (!(await isSiteAvailable(url))) {
throw new Error('Site is not available or blocked');
}
const browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const page = await browser.newPage();
await page.setViewport({ width: 1600, height: 1080 })
await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 });
// Делаем скриншот
const screenshot = await page.screenshot();
await browser.close();
return screenshot; // Возвращаем скриншот в виде Buffer
})
ipcMain.on('close-app', () => app.quit())
}
module.exports = { registerIPCHandlers };
async function isSiteAvailable(url) {
try {
const response = await fetch(url, { method: 'HEAD'});
return response.ok;
} catch (error) {
console.log('error , isSiteAvailable')
return false;
}
}