diff --git a/src/ipc/files.js b/src/ipc/files.js new file mode 100644 index 0000000..a486a0c --- /dev/null +++ b/src/ipc/files.js @@ -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 }; \ No newline at end of file diff --git a/src/ipc/folder.js b/src/ipc/folder.js new file mode 100644 index 0000000..dcfe085 --- /dev/null +++ b/src/ipc/folder.js @@ -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 }; \ No newline at end of file diff --git a/src/ipc/index.js b/src/ipc/index.js new file mode 100644 index 0000000..85de32a --- /dev/null +++ b/src/ipc/index.js @@ -0,0 +1,9 @@ +const folder = require('./folder'); +const files = require('./files'); + +function registerAll(mainWindow) { + folder.register(mainWindow); + files.register(); +} + +module.exports = { registerAll }; \ No newline at end of file diff --git a/src/main.js b/src/main.js index 386175f..08f1f6b 100644 --- a/src/main.js +++ b/src/main.js @@ -1,6 +1,6 @@ -const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron'); +const { app, BrowserWindow } = require('electron'); const path = require('path'); -const fs = require('fs'); +const { registerAll } = require('./ipc'); let mainWindow; @@ -27,7 +27,10 @@ function createWindow() { }); } -app.whenReady().then(createWindow); +app.whenReady().then(() => { + createWindow(); + registerAll(mainWindow); +}); app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); @@ -36,84 +39,3 @@ app.on('window-all-closed', () => { app.on('activate', () => { 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); -});