const { ipcMain, app } = require('electron'); const path = require('path'); const fs = require('fs'); const crypto = require('crypto'); const { fileExists, listImages, moveFileWithCopyFallback, readImageAsDataUrl, } = require('./file-utils'); const logger = require('../logger'); function register() { ipcMain.handle('list-images', async (_event, folderPath) => { logger.log('ipc:files', 'list-images requested', { folderPath }); try { const files = listImages(folderPath); logger.log('ipc:files', 'list-images result', { folderPath, count: files.length }); return files; } catch (e) { logger.error('ipc:files', `list-images failed: ${folderPath}`, e); return []; } }); ipcMain.handle('read-image', async (_event, filePath) => { try { return readImageAsDataUrl(filePath); } catch (e) { return null; } }); ipcMain.handle('check-files-exist', (_event, folderPath, names) => { return names.filter(name => fileExists(path.join(folderPath, name))); }); ipcMain.handle('move-files', async (_event, { targetFolder, files }) => { const errors = []; const moved = []; for (const file of files) { const destName = file.newName || file.name; const dest = path.join(targetFolder, destName); try { moveFileWithCopyFallback(file.path, dest); moved.push({ src: file.path, dest, name: destName }); } catch (e) { errors.push(`${file.name}: ${e.message}`); } } return { moved, errors }; }); // 삭제 → 스테이징 폴더로 이동 (undo 가능) ipcMain.handle('stage-delete-files', async (_event, filePaths) => { const batchDir = path.join(app.getPath('userData'), 'deleted_staging', Date.now().toString()); fs.mkdirSync(batchDir, { recursive: true }); const staged = []; const errors = []; for (const fp of filePaths) { const name = path.basename(fp); const dest = path.join(batchDir, name); try { moveFileWithCopyFallback(fp, dest); staged.push({ name, originalPath: fp, stagingPath: dest }); } catch (e) { errors.push(`${name}: ${e.message}`); } } if (!staged.length) try { fs.rmdirSync(batchDir); } catch {} return { staged, errors }; }); // 스테이징 파일 원래 위치로 복원 (undo delete) ipcMain.handle('restore-staged-files', async (_event, deletedEntries) => { const restored = []; const errors = []; for (const { name, originalPath, stagingPath } of deletedEntries) { if (fs.existsSync(originalPath)) { errors.push(`${name}: 같은 이름의 파일이 이미 존재합니다`); continue; } try { fs.mkdirSync(path.dirname(originalPath), { recursive: true }); moveFileWithCopyFallback(stagingPath, originalPath); restored.push(originalPath); } catch (e) { errors.push(`${name}: ${e.message}`); } } return { restored, errors }; }); ipcMain.handle('rename-files', async (_event, { folderPath, files, prefix, digits, postfix = '' }) => { const errors = []; const pad = (n) => String(n + 1).padStart(digits, '0'); const suffix = postfix ? `_${postfix}` : ''; // 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_${crypto.randomUUID()}${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)}${suffix}${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 };