리펙토링: IPC(Inter-Process Communication) 코드
This commit is contained in:
@@ -0,0 +1,74 @@
|
|||||||
|
const { ipcMain } = require('electron');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const EXTS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.tif', '.webp', '.heic', '.heif']);
|
||||||
|
|
||||||
|
function register() {
|
||||||
|
ipcMain.handle('list-images', async (_event, folderPath) => {
|
||||||
|
try {
|
||||||
|
const files = fs.readdirSync(folderPath)
|
||||||
|
.filter(f => EXTS.has(path.extname(f).toLowerCase()))
|
||||||
|
.map(f => {
|
||||||
|
const fullPath = path.join(folderPath, f);
|
||||||
|
const stat = fs.statSync(fullPath);
|
||||||
|
return { name: f, path: fullPath, mtime: stat.mtimeMs, size: stat.size };
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name, 'ko'));
|
||||||
|
return files;
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('read-image', async (_event, filePath) => {
|
||||||
|
try {
|
||||||
|
const data = fs.readFileSync(filePath);
|
||||||
|
const ext = path.extname(filePath).toLowerCase().replace('.', '');
|
||||||
|
const mime = (ext === 'jpg' || ext === 'jpeg') ? 'jpeg'
|
||||||
|
: ext === 'png' ? 'png'
|
||||||
|
: ext === 'gif' ? 'gif'
|
||||||
|
: ext === 'webp' ? 'webp'
|
||||||
|
: 'jpeg';
|
||||||
|
return `data:image/${mime};base64,${data.toString('base64')}`;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('rename-files', async (_event, { folderPath, files, prefix, digits }) => {
|
||||||
|
const errors = [];
|
||||||
|
const pad = (n) => String(n + 1).padStart(digits, '0');
|
||||||
|
|
||||||
|
// 1단계: 임시 이름으로 이동 (충돌 방지)
|
||||||
|
const tmpMap = [];
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const src = files[i].path;
|
||||||
|
const ext = path.extname(files[i].name).toLowerCase();
|
||||||
|
const tmp = path.join(folderPath, `__tmp_rename_${i}_${Date.now()}${ext}`);
|
||||||
|
try {
|
||||||
|
fs.renameSync(src, tmp);
|
||||||
|
tmpMap.push({ tmp, ext, idx: i });
|
||||||
|
} catch (e) {
|
||||||
|
errors.push(`${files[i].name}: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2단계: 최종 이름으로 이동
|
||||||
|
const renamed = [];
|
||||||
|
for (const { tmp, ext, idx } of tmpMap) {
|
||||||
|
const newName = `${prefix}_${pad(idx)}${ext}`;
|
||||||
|
const dest = path.join(folderPath, newName);
|
||||||
|
try {
|
||||||
|
fs.renameSync(tmp, dest);
|
||||||
|
renamed.push({ oldPath: tmp, newPath: dest, newName });
|
||||||
|
} catch (e) {
|
||||||
|
errors.push(`${newName}: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { renamed, errors };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { register };
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
const { ipcMain, dialog, shell } = require('electron');
|
||||||
|
|
||||||
|
function register(mainWindow) {
|
||||||
|
ipcMain.handle('select-folder', async () => {
|
||||||
|
const result = await dialog.showOpenDialog(mainWindow, {
|
||||||
|
properties: ['openDirectory'],
|
||||||
|
title: '이미지 폴더 선택',
|
||||||
|
});
|
||||||
|
if (result.canceled) return null;
|
||||||
|
return result.filePaths[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('reveal-folder', async (_event, folderPath) => {
|
||||||
|
shell.openPath(folderPath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { register };
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
const folder = require('./folder');
|
||||||
|
const files = require('./files');
|
||||||
|
|
||||||
|
function registerAll(mainWindow) {
|
||||||
|
folder.register(mainWindow);
|
||||||
|
files.register();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { registerAll };
|
||||||
+6
-84
@@ -1,6 +1,6 @@
|
|||||||
const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
|
const { app, BrowserWindow } = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const { registerAll } = require('./ipc');
|
||||||
|
|
||||||
let mainWindow;
|
let mainWindow;
|
||||||
|
|
||||||
@@ -27,7 +27,10 @@ function createWindow() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
app.whenReady().then(createWindow);
|
app.whenReady().then(() => {
|
||||||
|
createWindow();
|
||||||
|
registerAll(mainWindow);
|
||||||
|
});
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
if (process.platform !== 'darwin') app.quit();
|
if (process.platform !== 'darwin') app.quit();
|
||||||
@@ -36,84 +39,3 @@ app.on('window-all-closed', () => {
|
|||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── IPC 핸들러 ──────────────────────────────────────
|
|
||||||
|
|
||||||
ipcMain.handle('select-folder', async () => {
|
|
||||||
const result = await dialog.showOpenDialog(mainWindow, {
|
|
||||||
properties: ['openDirectory'],
|
|
||||||
title: '이미지 폴더 선택',
|
|
||||||
});
|
|
||||||
if (result.canceled) return null;
|
|
||||||
return result.filePaths[0];
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('list-images', async (_event, folderPath) => {
|
|
||||||
const EXTS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.tif', '.webp', '.heic', '.heif']);
|
|
||||||
try {
|
|
||||||
const files = fs.readdirSync(folderPath)
|
|
||||||
.filter(f => EXTS.has(path.extname(f).toLowerCase()))
|
|
||||||
.map(f => {
|
|
||||||
const fullPath = path.join(folderPath, f);
|
|
||||||
const stat = fs.statSync(fullPath);
|
|
||||||
return { name: f, path: fullPath, mtime: stat.mtimeMs, size: stat.size };
|
|
||||||
})
|
|
||||||
.sort((a, b) => a.name.localeCompare(b.name, 'ko'));
|
|
||||||
return files;
|
|
||||||
} catch (e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('read-image', async (_event, filePath) => {
|
|
||||||
try {
|
|
||||||
const data = fs.readFileSync(filePath);
|
|
||||||
const ext = path.extname(filePath).toLowerCase().replace('.', '');
|
|
||||||
const mime = (ext === 'jpg' || ext === 'jpeg') ? 'jpeg'
|
|
||||||
: ext === 'png' ? 'png'
|
|
||||||
: ext === 'gif' ? 'gif'
|
|
||||||
: ext === 'webp' ? 'webp'
|
|
||||||
: 'jpeg';
|
|
||||||
return `data:image/${mime};base64,${data.toString('base64')}`;
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('rename-files', async (_event, { folderPath, files, prefix, digits }) => {
|
|
||||||
const errors = [];
|
|
||||||
const pad = (n) => String(n + 1).padStart(digits, '0');
|
|
||||||
|
|
||||||
// 1단계: 임시 이름으로 이동 (충돌 방지)
|
|
||||||
const tmpMap = [];
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
const src = files[i].path;
|
|
||||||
const ext = path.extname(files[i].name).toLowerCase();
|
|
||||||
const tmp = path.join(folderPath, `__tmp_rename_${i}_${Date.now()}${ext}`);
|
|
||||||
try {
|
|
||||||
fs.renameSync(src, tmp);
|
|
||||||
tmpMap.push({ tmp, ext, idx: i });
|
|
||||||
} catch (e) {
|
|
||||||
errors.push(`${files[i].name}: ${e.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2단계: 최종 이름으로 이동
|
|
||||||
const renamed = [];
|
|
||||||
for (const { tmp, ext, idx } of tmpMap) {
|
|
||||||
const newName = `${prefix}_${pad(idx)}${ext}`;
|
|
||||||
const dest = path.join(folderPath, newName);
|
|
||||||
try {
|
|
||||||
fs.renameSync(tmp, dest);
|
|
||||||
renamed.push({ oldPath: tmp, newPath: dest, newName });
|
|
||||||
} catch (e) {
|
|
||||||
errors.push(`${newName}: ${e.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { renamed, errors };
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('reveal-folder', async (_event, folderPath) => {
|
|
||||||
shell.openPath(folderPath);
|
|
||||||
});
|
|
||||||
|
|||||||
Reference in New Issue
Block a user